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#

idstring

Unique identifier, prefixed with wh_.

urlstring

The HTTPS endpoint deliveries are POSTed to.

eventsstring[]

The event types this endpoint receives, or ["*"] for all events.

secretstring

The signing secret used to compute the CashSDK-Signature header. Returned only when the endpoint is created.

created_atstring

ISO 8601 creation timestamp.

List webhook endpoints#

GET /v1/webhooks

bash
curl https://api.cashsdk.com/v1/webhooks \
  -H "Authorization: Bearer csk_sk_..."
json
{
  "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

urlstringrequired

The HTTPS URL to deliver events to.

eventsstring[]required

Event types to subscribe to, or ["*"] for all. See Events for the full list.

bash
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": ["*"]
  }'
json
{
  "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}

idstringrequired

The webhook endpoint ID, e.g. wh_4Tz9.

bash
curl -X DELETE https://api.cashsdk.com/v1/webhooks/wh_4Tz9 \
  -H "Authorization: Bearer csk_sk_..."
json
{
  "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-2xx responses (or timeouts) are retried with exponential backoff over several hours.
  • Idempotency. Retries reuse the same event id. Deduplicate by id and 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.

javascript
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);
  }
);

Next steps#