Unique identifier, prefixed with wh_.
Webhooks
Register webhook endpoints, receive events, and verify signatures.
Webhooks push events to your server as they happen — purchases, renewals, cancellations, refunds, and entitlement changes. Register an HTTPS endpoint, subscribe to the event types you care about, and verify the signature on every delivery.
The webhook object#
idstringurlstringThe HTTPS endpoint deliveries are POSTed to.
eventsstring[]The event types this endpoint receives, or ["*"] for all events.
secretstringThe signing secret used to compute the CashSDK-Signature header. Returned
only when the endpoint is created.
created_atstringISO 8601 creation timestamp.
List webhook endpoints#
GET /v1/webhooks
curl https://api.cashsdk.com/v1/webhooks \
-H "Authorization: Bearer csk_sk_..."
{
"data": [
{
"id": "wh_4Tz9",
"url": "https://example.com/hooks/cashsdk",
"events": ["*"],
"created_at": "2026-06-10T08:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}
Register a webhook endpoint#
POST /v1/webhooks
urlstringrequiredThe HTTPS URL to deliver events to.
eventsstring[]requiredEvent types to subscribe to, or ["*"] for all. See
Events for the full list.
curl -X POST https://api.cashsdk.com/v1/webhooks \
-H "Authorization: Bearer csk_sk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/cashsdk",
"events": ["*"]
}'
{
"id": "wh_4Tz9",
"url": "https://example.com/hooks/cashsdk",
"events": ["*"],
"secret": "whsec_Hs8...redacted",
"created_at": "2026-06-10T08:00:00Z"
}
The signing secret is shown only once, at creation. Store it securely — you
need it to verify deliveries. If you lose it, delete the endpoint and register
a new one.
Delete a webhook endpoint#
DELETE /v1/webhooks/{id}
idstringrequiredThe webhook endpoint ID, e.g. wh_4Tz9.
curl -X DELETE https://api.cashsdk.com/v1/webhooks/wh_4Tz9 \
-H "Authorization: Bearer csk_sk_..."
{
"id": "wh_4Tz9",
"deleted": true
}
Delivery, retries, and idempotency#
Each delivery is an HTTP POST whose body is a single event
object. Respond with a 2xx status
promptly to acknowledge receipt.
- Retries. Non-
2xxresponses (or timeouts) are retried with exponential backoff over several hours. - Idempotency. Retries reuse the same event
id. Deduplicate byidand process each event at most once. - Ordering. Events are generally delivered in order, but retries mean you should not rely on strict ordering — reconcile against the object's current state when it matters.
Verifying signatures#
Every delivery includes a CashSDK-Signature header — an HMAC-SHA256 of the raw
request body, keyed by your endpoint's signing secret. Compute the expected
signature over the raw body and compare in constant time before trusting the
payload.
import crypto from "node:crypto";
// `rawBody` MUST be the exact bytes received, not a re-serialized object.
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express example
app.post(
"/hooks/cashsdk",
express.raw({ type: "application/json" }),
(req, res) => {
const ok = verifyWebhook(
req.body, // Buffer of the raw body
req.header("CashSDK-Signature"),
process.env.CASHSDK_WEBHOOK_SECRET
);
if (!ok) return res.status(400).send("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// ... handle event.type, dedupe by event.id ...
res.sendStatus(200);
}
);
Reject any request whose signature does not match, and process each event.id
only once. See the Webhooks guide for a full handler.