# Guide to Chargebee Webhooks: Features and Best Practices

Chargebee webhooks notify your application about subscription and billing activity: a subscription is created or cancelled, a payment succeeds or fails, an invoice is generated. If you're building on Chargebee Billing, webhooks are how you react to these events without polling.

This guide covers how Chargebee webhooks work, the events you'll handle, why there's no signature to verify (and what to do instead), and the best practices for production.

## What are Chargebee webhooks?

Chargebee webhooks are JSON POSTs delivered to a URL you configure. There is no HMAC signature and no signature header of any kind: Chargebee's docs state that HMAC signing is not supported. Any integration claiming an `hmac-signature` for Chargebee is wrong. Authenticity instead rests on non-cryptographic, opt-in layers: HTTP Basic Auth credentials on the webhook URL, an older secret-in-URL pattern, and region-specific sending IP ranges for firewall allowlisting. Because nothing is signed, there's no timestamp, no replay window, and no raw-body concern.

## Chargebee webhook features

| Feature | Details |
| --- | --- |
| Configuration | Settings > Webhooks (up to 5 endpoints) |
| Verification | None cryptographic. Optional Basic Auth (off by default), secret-in-URL, IP allowlist |
| Event field | Top-level `event_type` (snake_case, not dotted) |
| Payload | Shape varies by the site's `api_version` (v1/v2) and `chargebee_response_schema_type` |
| Card data | Masked by default |
| Retries | 7 attempts at 2min, 6min, 30min, 1hr, 5hr, 1day, 2days; endpoint must return 2xx |
| SDK | npm `chargebee`, pip `chargebee` (no verification helper, because nothing is signed) |

## Common events

Chargebee event names are snake_case in the top-level `event_type` field (not dotted):

| Event | Fires when |
| --- | --- |
| `subscription_created` / `subscription_activated` | A subscription is created or activated |
| `subscription_changed` / `subscription_cancelled` / `subscription_renewed` | A subscription changes, cancels, or renews |
| `invoice_generated` | An invoice is generated |
| `payment_succeeded` / `payment_failed` | A payment resolves |
| `customer_created` / `customer_deleted` | A customer is created or deleted |
| `payment_source_expiring` | A payment source is about to expire |

The envelope carries `id`, `occurred_at`, `source`, `event_type`, `api_version`, and `content`.

## Setting up Chargebee webhooks

In the Chargebee dashboard, go to Settings > Webhooks > Add Webhook, set a name and HTTPS URL, and choose the events. To add Basic Auth, toggle "Protect webhook URL with basic authentication" on and set a username and password. This toggle is off by default, so an unconfigured Chargebee webhook has no verification at all. Chargebee also publishes region-specific (US / EU / AU) sending IP ranges you can allowlist.

## Securing Chargebee webhooks

There's no signature, so the strongest option Chargebee offers is HTTP Basic Auth. Configure it on the endpoint, then check the `Authorization` header on every request with a constant-time comparison. Serve the endpoint over HTTPS and treat the credentials as secrets. Layer the IP allowlist on top.

```javascript
const crypto = require("crypto");

const USER = process.env.CHARGEBEE_WEBHOOK_USERNAME;
const PASS = process.env.CHARGEBEE_WEBHOOK_PASSWORD;

function safeEqual(a, b) {
  const ba = Buffer.from(a || "");
  const bb = Buffer.from(b || "");
  return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}

function verifyBasicAuth(header) {
  if (!header || !header.startsWith("Basic ")) return false;
  const decoded = Buffer.from(header.slice(6), "base64").toString("utf8");
  const i = decoded.indexOf(":"); // split on the FIRST colon (passwords may contain ':')
  return safeEqual(decoded.slice(0, i), USER) && safeEqual(decoded.slice(i + 1), PASS);
}

app.post("/webhooks/chargebee", express.json(), (req, res) => {
  if (!verifyBasicAuth(req.headers["authorization"])) return res.sendStatus(401);

  res.sendStatus(200); // acknowledge fast (2xx)
  processQueue.add(req.body); // branch on event_type, async
});

```

The same check in Python:

```python
import base64
import hmac
import os

USER = os.environ["CHARGEBEE_WEBHOOK_USERNAME"]
PASS = os.environ["CHARGEBEE_WEBHOOK_PASSWORD"]

def verify_basic_auth(header: str) -> bool:
    if not header or not header.startswith("Basic "):
        return False
    decoded = base64.b64decode(header[6:]).decode("utf-8")
    user, _, password = decoded.partition(":")  # split on the first colon
    return hmac.compare_digest(user, USER) and hmac.compare_digest(password, PASS)

```

## Chargebee webhook limitations and pain points

### There's no signature, and auth is off by default

The Problem: Chargebee doesn't sign webhooks, and Basic Auth is opt-in and off by default. An unconfigured endpoint accepts anything, so you can't prove a request came from Chargebee.

Why It Happens: HMAC signing isn't supported; Basic Auth is a toggle you must enable.

Workarounds:

* Enable Basic Auth, verify it in constant time, serve HTTPS only, and allowlist Chargebee's region IP ranges.

How Hookdeck Can Help: Hookdeck sits in front of Chargebee and adds verification, filtering, and delivery observability the source can't provide, turning an unsigned webhook into a monitored, replayable stream.

### The payload shape varies per site

The Problem: The body shape depends on the site's `api_version` (v1 vs v2) and its `chargebee_response_schema_type`. Two Chargebee sites can emit structurally different bodies for the same `event_type`.

Why It Happens: Chargebee renders the payload according to per-site settings.

Workarounds:

* Pin to a known `api_version`, and read defensively rather than assuming one shape.

How Hookdeck Can Help: Hookdeck's transformations can normalize varying payloads into a consistent shape before your app sees them.

### Credentials can leak

The Problem: Basic Auth credentials (and the older secret-in-URL) can end up in logs and proxies, and the URL secret is the weaker of the two.

Why It Happens: The credential travels with each request; the URL secret sits in the query string.

Workarounds:

* Prefer Basic Auth over the URL secret, keep credentials out of logs, and rotate on exposure.

How Hookdeck Can Help: Hookdeck can hold the check centrally, so credentials aren't spread across every consumer's logs.

### Duplicates and out-of-order delivery

The Problem: Chargebee retries up to 7 times and can deliver out of order, so the same event can arrive more than once.

Why It Happens: At-least-once delivery with a fixed retry schedule.

Workarounds:

* Dedupe on the event `id` and make handlers idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Best practices

### Enable and verify Basic Auth

Turn on Basic Auth (it's off by default), and compare the `Authorization` header in constant time.

### Allowlist the region IP ranges

Restrict to Chargebee's US / EU / AU sending IP ranges as defense in depth.

### Read the payload defensively

The shape varies by `api_version` and `chargebee_response_schema_type`, so don't assume one structure.

### Acknowledge fast, process asynchronously

Return 2xx quickly and defer work to a queue; dedupe on `id`. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Chargebee webhooks have no HMAC signature: authenticity rests on opt-in HTTP Basic Auth (off by default), an older secret-in-URL option, and region IP allowlisting. Enable Basic Auth and verify it in constant time, allowlist the sending IPs, read the `api_version`-dependent payload defensively, and dedupe on `id`. Because the source offers no verification or observability of its own, it's a strong case for a gateway in front.

[Hookdeck](https://hookdeck.com) adds verification, deduplication, and delivery observability to Chargebee's unsigned webhooks, so your app processes a trustworthy, monitored stream.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Chargebee webhooks reliably in minutes.