Gareth Wilson Gareth Wilson

Guide to Recharge Webhooks: Features and Best Practices

Published


Recharge webhooks notify your systems about subscription commerce activity: a subscription is created, a charge is paid, an order is processed. If you're provisioning access, syncing subscription state, or triggering fulfillment off Recharge, webhooks are how you react without polling the API.

This guide covers how Recharge webhooks work, the topics you can subscribe to, and one detail that trips up almost everyone: the X-Recharge-Hmac-Sha256 header is not a true HMAC. Get that wrong and every delivery fails validation. We also cover the retry behavior and the best practices for production.

What are Recharge webhooks?

Recharge webhooks are HTTP POSTs sent to an address you register, each carrying a JSON payload about a subscription, charge, or order event. You create webhooks through the Recharge Admin API, subscribing each to a specific topic.

The validation scheme is unusual, so read the security section carefully before you write the check.

Recharge webhook features

FeatureDetails
ConfigurationAdmin API: POST https://api.rechargeapps.com/webhooks
Validation headerX-Recharge-Hmac-Sha256
Validation schemePlain SHA-256 of client_secret + raw_body (secret first), hex-encoded
API versions2021-01 and 2021-11 share tokens and topic names
AcknowledgementRespond 200 within 5 seconds
Retries20 times over 48 hours, then the webhook subscription is deleted
Failure conditionsNo response, 408, 429, or 5xx
SDKNo webhook-relevant official SDK

Common topics

Recharge topics are slash-separated and scoped by resource. Subscription, charge, and order topics are the common ones:

TopicFires when
subscription/createdA new subscription is created
subscription/cancelledA subscription is cancelled
charge/createdA charge is created
charge/paidA charge is successfully paid
charge/failedA charge fails
order/createdAn order is created
order/processedAn order is processed

Note the paid-charge topic is charge/paid. There is no charge/success topic; if you see it referenced anywhere, it's a legacy name. Subscribe only to the topics you process, and treat the Recharge events reference as authoritative for the full list.

Setting up Recharge webhooks

Webhooks are created via the Admin API with an address and a topic:

curl -X POST "https://api.rechargeapps.com/webhooks" \
  -H "X-Recharge-Access-Token: $RECHARGE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "address": "https://your-app.example.com/webhook", "topic": "charge/paid" }'

API versions 2021-01 and 2021-11 share the same token and topic names. Respond to deliveries with a 200 within 5 seconds.

Securing Recharge webhooks

Here's the trap. Despite the header name X-Recharge-Hmac-Sha256, the value is not an HMAC. It's a plain SHA-256 hash of your API client secret concatenated with the raw request body, with the secret first, then the body, hex-encoded. Reach for hmac(secret, body) (the natural default) and you'll reject every legitimate delivery.

const crypto = require("crypto");

const CLIENT_SECRET = process.env.RECHARGE_CLIENT_SECRET;

function verify(rawBody, signature) {
  // NOT an HMAC: sha256(client_secret + raw_body), secret first
  const expected = crypto
    .createHash("sha256")
    .update(CLIENT_SECRET)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-recharge-hmac-sha256"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge within 5 seconds
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

The same check in Python, matching Recharge's own example:

import hashlib
import hmac
import os

CLIENT_SECRET = os.environ["RECHARGE_CLIENT_SECRET"]


def verify(raw_body: bytes, signature: str) -> bool:
    # secret first, then body; plain SHA-256, hex
    h = hashlib.sha256()
    h.update(CLIENT_SECRET.encode("utf-8"))
    h.update(raw_body)
    return hmac.compare_digest(h.hexdigest(), signature or "")

The raw body must be byte-exact; validation fails "even if one space is lost," so verify before any parsing or re-serialization.

Recharge webhook limitations and pain points

The signature is not an HMAC

The Problem: The header is named X-Recharge-Hmac-Sha256, but it's a plain SHA-256 of client_secret + raw_body. Every developer who assumes a standard HMAC gets a validation that never passes.

Why It Happens: Recharge's scheme concatenates the secret and body and hashes them, rather than using HMAC's keyed construction.

Workarounds:

  • Use sha256(client_secret + raw_body) with the secret first; do not use hmac().
  • Verify against the exact raw bytes and compare in constant time.

How Hookdeck Can Help: Hookdeck verifies the Recharge signature at the edge, so your application receives pre-verified events and you never have to encode this non-standard scheme yourself.

A 5-second timeout and deletion after failures

The Problem: Recharge waits 5 seconds for a 200 and retries a failed delivery 20 times over 48 hours, then deletes the webhook subscription. A persistently failing endpoint doesn't just miss events; it loses the subscription and stops receiving anything.

Why It Happens: Recharge prunes subscriptions whose endpoints look permanently broken.

Workarounds:

  • Acknowledge with a 200 fast and process asynchronously so transient issues don't burn the retry budget.
  • Monitor for missing deliveries and be ready to recreate a deleted webhook.

How Hookdeck Can Help: Hookdeck always accepts deliveries from Recharge and absorbs downstream failures itself, keeping the subscription alive while it retries to your service, so an outage never costs you the webhook.

Duplicates over the retry window

The Problem: With up to 20 retries over 48 hours, the same event can arrive multiple times. Acting on duplicates double-provisions or double-charges downstream.

Why It Happens: At-least-once delivery favors not losing events over exactly-once semantics.

Workarounds:

  • Dedupe on an event or resource identifier from the payload.
  • Make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so the 48-hour retry window doesn't turn into repeated processing. See our guide to webhook idempotency.

No delivery log to recover from

The Problem: Once retries are exhausted (or the subscription is deleted), missed events aren't recoverable from Recharge.

Why It Happens: Recharge doesn't retain a durable, replayable delivery log for consumers.

Workarounds:

  • Log every received delivery so you have your own record.
  • Reconcile against the Recharge API for critical state.

How Hookdeck Can Help: Hookdeck durably stores every delivery and supports replay, so recovery doesn't depend on Recharge's retry window.

Best practices

Validate with sha256(secret + body), not HMAC

Hash the client secret followed by the raw body with plain SHA-256, hex-encode, and compare in constant time. This is the single most important detail for Recharge.

Acknowledge within five seconds, process asynchronously

Return 200 fast and defer work to a queue so transient slowness doesn't push you toward the 20-failure deletion. See why to process webhooks asynchronously.

Use the correct topic names

Subscribe to charge/paid for paid charges; charge/success does not exist.

Dedupe and reconcile

Dedupe on a payload identifier, make side effects idempotent, and reconcile against the API for anything you can't afford to miss.

Make Recharge webhooks production-ready

Hookdeck verifies the non-standard signature, deduplicates, and durably queues every subscription event

Conclusion

Recharge webhooks give you subscription, charge, and order activity in real time, but two details decide whether the integration works: the X-Recharge-Hmac-Sha256 header is a plain sha256(client_secret + raw_body), not an HMAC, and a persistently failing endpoint gets its subscription deleted after 20 retries over 48 hours. Add duplicates and no recoverable delivery log, and reliability rests on you.

That means correct validation, fast acknowledgement, deduplication, and reconciliation. Hookdeck verifies the signature, deduplicates, and durably queues every event with replay at the edge, so your application only ever processes verified, unique events and a bad day downstream never deletes your Recharge webhook.

Get started with Hookdeck for free and handle Recharge webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.