Webhook Basics
Ching POSTs events to URLs you register. Each delivery is signed with your endpoint's secret so you can trust its origin.
Event Shape
{
"id": "evt_m2n3o4p5q6r7",
"type": "charge.succeeded",
"data": {
"id": "ch_9mTPfRSDmEOU",
"amount": 9900,
"currency": "ils",
"customer": "cus_V8ltq1pK_MWH"
},
"livemode": false,
"created": "2026-04-19T09:15:22.000Z"
}Verify the Signature
Every delivery includes a Ching-Signature header whose value is HMAC-SHA256(raw_body, endpoint_secret) as a lowercase hex digest. Compute the same on your side and compare with a timing-safe check:
Node.js
import crypto from "node:crypto";
export function verifyChingSignature(
rawBody: string,
signature: string,
secret: string,
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signature, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Use the raw body: Verify against the exact bytes Ching sent, not the JSON-parsed object. Parse after verifying.
Event Types
The most common events to subscribe to:
| Type | Fires when |
|---|---|
charge.succeeded | A charge completed successfully |
charge.failed | A charge was declined |
refund.succeeded | A refund landed at the provider |
setup_session.succeeded | A customer finished adding a card |
subscription.created | A new subscription started (including trial) |
subscription.updated | Plan change, cancel-at-period-end toggle, renewal |
subscription.canceled | A subscription ended |
checkout_session.completed | A hosted checkout was paid or applied |
payment_method.detached | A customer removed a card |
Subscribe to ["*"] to receive every event.
Retries
Deliveries time out after 10 seconds. If your endpoint returns a non-2xx or times out, Ching retries up to 3 total attempts via a background cron. To be a good webhook consumer:
- Return
200as soon as you have persisted the event id. - Do heavy work asynchronously - don't make Ching wait for your database, email provider, or third-party API.
- Deduplicate by
event.id- a retry after a 500 will re-send the same id.