Agent skill
Recharge Webhooks Skill
Receive and verify Recharge (subscription commerce) webhooks. Use when setting up Recharge webhook handlers, debugging X-Recharge-Webhook-Signature or legacy X-Recharge-Hmac-Sha256 signature verification, or handling subscription events like charge/paid, charge/failed, subscription/created, subscription/cancelled, and order/created.
Install this skill
npx skills add hookdeck/webhook-skills --skill recharge-webhooks
When to Use This Skill
- How do I receive Recharge webhooks?
- How do I verify Recharge webhook signatures?
- Why is my
X-Recharge-Webhook-SignatureorX-Recharge-Hmac-Sha256verification failing? - How do I handle
charge/paid,charge/failed, orsubscription/cancelledevents? - How do I create a Recharge webhook subscription via the API?
Verification (core)
Every webhook delivery includes two signature schemes: a recommended timestamp-bound scheme (use this for all new integrations) and a legacy body-only scheme that remains supported.
Legacy: body-only scheme (X-Recharge-Hmac-Sha256)
For backward compatibility, every webhook also includes the legacy X-Recharge-Hmac-Sha256 header. Fall back to it only when the new header is absent.
The biggest gotcha: despite the header name, this is NOT a true HMAC. It is a plain SHA-256 hash of the API Client Secret concatenated with the raw request body — secret first, then body — hex-encoded. Use sha256(secret + rawBody), not hmac(secret, rawBody). Always hash the raw body bytes; verification fails "even if one space is lost".
Node:
function verifyRechargeWebhookLegacy(rawBody, signatureHeader, clientSecret) {
if (!signatureHeader) return false;
// Plain SHA-256 of (clientSecret + rawBody), NOT HMAC. Secret is prepended.
const digest = crypto.createHash('sha256').update(clientSecret).update(rawBody).digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signatureHeader));
} catch {
return false; // length mismatch = invalid
}
}
Python:
def verify_recharge_webhook_legacy(raw_body: bytes, signature_header: str, client_secret: str) -> bool:
if not signature_header:
return False
# Plain SHA-256 of (client_secret + raw_body), NOT HMAC. Secret is prepended.
digest = hashlib.sha256(client_secret.encode("utf-8") + raw_body).hexdigest()
return hmac.compare_digest(digest, signature_header)
There is no official Recharge SDK for webhook verification (@rechargeapps/storefront-client covers the Storefront API only), so verify manually as above.
Dispatching events
Recharge does not send a documented topic/action header. Payloads wrap the resource by a top-level key — {"charge": {…}}, {"order": {…}}, {"subscription": {…}} — so dispatch on that key. If your handler needs the exact action (created vs updated vs paid), register a distinct endpoint path per topic when creating the webhook subscription (POST /webhooks with a different address per topic).
Respond with
200within 5 seconds. No response,408,429, or5xxcounts as failure. Recharge retries the same webhook 20 times over 48 hours, then deletes the subscription. Do slow work asynchronously and return200immediately.
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types (Topics)
Topics use a resource/action format. Subscribe to only what you need.
| Topic | Triggered When |
|---|---|
charge/created | A charge is queued for an upcoming order |
charge/paid | A charge is successfully paid (use this, not the legacy charge/success) |
charge/failed | A charge attempt fails |
charge/max_retries_reached | A charge exhausted its retry attempts (dunning) |
subscription/created | A subscription is created |
subscription/cancelled | A subscription is cancelled |
subscription/updated | A subscription is modified |
order/created | An order is created |
order/processed | An order is processed |
customer/updated | Customer details change |
For the full topic list, see Available webhooks and references/overview.md.
Environment Variables
# API Client Secret from the Recharge Dashboard → Integrations → API Tokens →
# click your token (Edit API Token page). This is NOT the API access token.
RECHARGE_API_CLIENT_SECRET=your_api_client_secret_here
Creating a Webhook Subscription
Webhooks are registered via the Admin API (one subscription per topic):
curl 'https://api.rechargeapps.com/webhooks' \
-H 'X-Recharge-Version: 2021-11' \
-H 'X-Recharge-Access-Token: your_api_token' \
-H 'Content-Type: application/json' \
-d '{
"address": "https://your-app.com/webhooks/recharge",
"topic": "charge/paid",
"included_objects": ["customer"]
}'
Local Development
# Start a tunnel (no account needed)
npx hookdeck-cli listen 3000 recharge --path /webhooks/recharge
Reference Materials
- references/overview.md - Recharge webhook concepts, topics, payload shape
- references/setup.md - Get the API Client Secret, register subscriptions
- references/verification.md - Signature verification details and gotchas