# 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-Signature` or `X-Recharge-Hmac-Sha256` verification failing?
* How do I handle `charge/paid`, `charge/failed`, or `subscription/cancelled` events?
* 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:

```javascript
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:

```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 `200` within 5 seconds. No response, `408`, `429`, or `5xx` counts as failure.
> Recharge retries the same webhook 20 times over 48 hours, then deletes the subscription. Do slow
> work asynchronously and return `200` immediately.

> For complete handlers with route wiring, event dispatch, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/recharge-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/recharge-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/recharge-webhooks/examples/fastapi/)

## 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](https://developer.rechargepayments.com/2021-11/webhooks_endpoints/webhooks_available)
> and [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/recharge-webhooks/references/overview.md).

## Environment Variables

```bash
# 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):

```bash
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

```bash
# Start a tunnel (no account needed)
npx hookdeck-cli listen 3000 recharge --path /webhooks/recharge

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/recharge-webhooks/references/overview.md) - Recharge webhook concepts, topics, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/recharge-webhooks/references/setup.md) - Get the API Client Secret, register subscriptions
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/recharge-webhooks/references/verification.md) - Signature verification details and gotchas