Guide to Recharge Webhooks: Features and Best Practices
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
| Feature | Details |
|---|---|
| Configuration | Admin API: POST https://api.rechargeapps.com/webhooks |
| Validation header | X-Recharge-Hmac-Sha256 |
| Validation scheme | Plain SHA-256 of client_secret + raw_body (secret first), hex-encoded |
| API versions | 2021-01 and 2021-11 share tokens and topic names |
| Acknowledgement | Respond 200 within 5 seconds |
| Retries | 20 times over 48 hours, then the webhook subscription is deleted |
| Failure conditions | No response, 408, 429, or 5xx |
| SDK | No webhook-relevant official SDK |
Common topics
Recharge topics are slash-separated and scoped by resource. Subscription, charge, and order topics are the common ones:
| Topic | Fires when |
|---|---|
subscription/created | A new subscription is created |
subscription/cancelled | A subscription is cancelled |
charge/created | A charge is created |
charge/paid | A charge is successfully paid |
charge/failed | A charge fails |
order/created | An order is created |
order/processed | An 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 usehmac(). - 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.