# Webhooks Credyt can notify your platform in real time when billing events occur. You register one or more webhook destinations — HTTPS endpoints in your infrastructure — and Credyt delivers a signed HTTP `POST` request each time a matching event fires. ## Create a Webhook Destination[​](#create-a-webhook-destination "Direct link to Create a Webhook Destination") [API Reference](https://docs.credyt.ai/api/webhooks-create-webhook-destination.md) * REST API * TypeScript SDK * Python SDK POST https\://api.credyt.ai/webhooks ```json { "url": "https://yourplatform.com/webhooks/credyt", "topics": [ "*" ] } ``` **Response** ```json { "id": "whd_01abc123", "secret": "whsec_••••••", "created_at": "2026-04-14T10:00:00Z" } ``` ```typescript await client.webhooks.createWebhookDestination({ url: "https://yourplatform.com/webhooks/credyt", topics: [ "*", ], }); ``` [View full sample on GitHub](https://github.com/credyt/sdk-ts/blob/main/samples/webhooksWebhooksCreateWebhookDestinationSample.ts#L44) **SDK response object** ```typescript const response = { id: "whd_01abc123", secret: "whsec_••••••", createdAt: "2026-04-14T10:00:00Z", }; ``` ```python response = client.webhooks.create_webhook_destination( body={ "url": "https://yourplatform.com/webhooks/credyt", "topics": [ "*", ], }, ) ``` **SDK response object** ```python response = { "id": "whd_01abc123", "secret": "whsec_••••••", "created_at": "2026-04-14T10:00:00Z", } ``` Save your secret The `secret` is returned at creation time and can also be retrieved via `GET /webhooks/:webhookId`. Store it securely (e.g. as an environment variable) — it is required to verify webhook signatures. ## Available Webhook Events[​](#available-webhook-events "Direct link to Available Webhook Events") | Topic | Description | | ------------------------ | ---------------------------------------------------------------------------- | | `subscription.activated` | Fired when a subscription becomes active and includes the activated products | ### Event envelope[​](#event-envelope "Direct link to Event envelope") All webhook payloads share a common envelope: ```json { "id": "evt_01abc123", "type": "subscription.activated", "created": "2026-04-14T10:00:00Z", "live_mode": true, "data": { ... } } ``` ### `subscription.activated`[​](#subscriptionactivated "Direct link to subscriptionactivated") ```json { "id": "evt_01abc123", "type": "subscription.activated", "created": "2026-04-14T10:00:00Z", "live_mode": true, "data": { "customer_id": "cust_473cr1y0ghbyc3m1yfbwvn3nxx", "subscription_id": "sub_01abc123", "occurred_at": "2026-04-14T10:00:00Z", "products": [ { "product_rate_plan_id": "prp_4e28n8kk41931f5yt5em49ecw7", "product_code": "glitch_pro", "version": 1 }, { "product_rate_plan_id": "prp_4e28n8kk41931f5yt5em49ecw8", "product_code": "glitch_addon", "version": 2 } ] } } ``` ## Verifying Webhook Signatures[​](#verifying-webhook-signatures "Direct link to Verifying Webhook Signatures") Every webhook request includes a `Credyt-Signature` header you should use to confirm the request originated from Credyt and has not been tampered with. **Header format:** `t=,v0=` * `t` — Unix timestamp of when the event was dispatched * `v0` — HMAC-SHA256 hex signature of `${timestamp}.${rawBody}` using your destination secret The following Node.js helper verifies the signature: ```typescript import * as crypto from "crypto"; /** * Verify a Credyt webhook signature. * * @param secret - The webhook destination secret returned at creation * @param rawBody - The raw request body as a Uint8Array or Buffer * @param signatureHeader - The value of the `Credyt-Signature` header * @returns true if the signature is valid, false otherwise */ export function verifyWebhookSignature( secret: string, rawBody: Uint8Array, signatureHeader: string ): boolean { // Split on the first comma only: "t=,v0=" const [timestampPart, signaturesPart] = signatureHeader.split(",", 2); const timestamp = parseInt(timestampPart.replace("t=", ""), 10); if (isNaN(timestamp)) { return false; } const signatures = signaturesPart.replace("v0=", "").split(","); // Signed content is: "." const signedContent = `${timestamp}.${rawBody}`; const hmac = crypto.createHmac("sha256", secret); hmac.update(signedContent); const expectedSignature = hmac.digest("hex"); return signatures.some((sig) => sig === expectedSignature); } ``` **Express.js example** ```typescript import express from "express"; const app = express(); // Use raw body parser to preserve the exact bytes for signature verification app.post( "/webhooks/credyt", express.raw({ type: "application/json" }), (req, res) => { const signatureHeader = req.headers["credyt-signature"] as string; const isValid = verifyWebhookSignature( process.env.CREDYT_WEBHOOK_SECRET!, req.body, signatureHeader ); if (!isValid) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body.toString()); switch (event.topic) { case "subscription.activated": // Handle subscription activation break; } res.status(200).send(); } ); ``` Replay protection The `t` timestamp lets you reject replayed requests. Consider rejecting events where the timestamp is more than a few minutes old. ## Connect Webhooks[​](#connect-webhooks "Direct link to Connect Webhooks") If your platform uses Credyt's [C4P](https://docs.credyt.ai/partners/platforms-overview.md) functionality to manage sub-accounts, you can create dedicated webhook destinations for connected-account events by setting `connect: true` when creating the destination. * REST API * TypeScript SDK * Python SDK POST https\://api.credyt.ai/webhooks ```json { "url": "https://yourplatform.com/webhooks/credyt/connect", "topics": [ "subscription.activated" ], "connect": true } ``` **Response** ```json { "id": "dst_4kf9v6xp00hzc99rdwr5m97wce", "secret": "whsec_abc123def456", "created_at": "2026-04-14T10:00:00Z" } ``` ```typescript await client.webhooks.createWebhookDestination({ url: "https://yourplatform.com/webhooks/credyt/connect", topics: [ "subscription.activated", ], connect: true, }); ``` [View full sample on GitHub](https://github.com/credyt/sdk-ts/blob/main/samples/webhooksWebhooksCreateWebhookDestinationSample.ts#L27) **SDK response object** ```typescript const response = { id: "dst_4kf9v6xp00hzc99rdwr5m97wce", secret: "whsec_abc123def456", createdAt: "2026-04-14T10:00:00Z", }; ``` ```python response = client.webhooks.create_webhook_destination( body={ "url": "https://yourplatform.com/webhooks/credyt/connect", "topics": [ "subscription.activated", ], "connect": True, }, ) ``` **SDK response object** ```python response = { "id": "dst_4kf9v6xp00hzc99rdwr5m97wce", "secret": "whsec_abc123def456", "created_at": "2026-04-14T10:00:00Z", } ``` Connected-account events are routed **only** to destinations with `connect: true`. Standard destinations (with `connect: false`) receive platform-level events only. ## Testing Webhooks[​](#testing-webhooks "Direct link to Testing Webhooks") No endpoint yet? Tools like [webhook.site](https://webhook.site/) or [Hookdeck Console](https://console.hookdeck.com/) give you a temporary public URL that logs incoming requests — useful for inspecting the payload shape before you build your own endpoint. You can send a test event to an existing destination without triggering a real billing event. This is useful for verifying your endpoint handles the payload correctly. [API Reference](https://docs.credyt.ai/api/webhooks-test-webhook-destination.md) * REST API * TypeScript SDK * Python SDK POST https\://api.credyt.ai/webhooks/dst\_4kf9v6xp00hzc99rdwr5m97wce/test/subscription.activated ```http ``` **Response** ```json { "id": "evt_01abc123" } ``` ```typescript await client.webhooks.testWebhookDestination("dst_4kf9v6xp00hzc99rdwr5m97wce", "subscription.activated"); ``` [View full sample on GitHub](https://github.com/credyt/sdk-ts/blob/main/samples/webhooksWebhooksTestWebhookDestinationSample.ts#L11) **SDK response object** ```typescript const response = { id: "evt_01abc123", }; ``` ```python response = client.webhooks.test_webhook_destination( webhook_id="dst_4kf9v6xp00hzc99rdwr5m97wce", topic="subscription.activated", ) ``` **SDK response object** ```python response = { "id": "evt_01abc123", } ``` To test all destinations subscribed to a topic (without specifying a destination ID): [API Reference](https://docs.credyt.ai/api/webhooks-test-webhook-destinations.md) ```text POST https://api.credyt.ai/webhooks/test/:topic ``` Both endpoints return `202 Accepted` with the dispatched event ID: ```json { "id": "evt_01abc123" } ``` ## Managing Destinations[​](#managing-destinations "Direct link to Managing Destinations") | Operation | API Reference | | -------------------- | -------------------------------------------------------------------------------------------------------- | | List destinations | [GET /webhooks](https://docs.credyt.ai/api/webhooks-list-webhook-destinations.md) | | Get a destination | [GET /webhooks/:webhookId](https://docs.credyt.ai/api/webhooks-get-webhook-destination.md) | | Update a destination | [PATCH /webhooks/:webhookId](https://docs.credyt.ai/api/webhooks-update-webhook-destination.md) | | Delete a destination | [DELETE /webhooks/:webhookId](https://docs.credyt.ai/api/webhooks-delete-webhook-destination.md) | | List webhook events | [GET /webhooks/events](https://docs.credyt.ai/api/webhooks-list-events.md) | ### Webhook Event Log[​](#webhook-event-log "Direct link to Webhook Event Log") The event log (`GET /webhooks/events`) lets you inspect every delivery attempt, including its status (`Success` or `Failed`) and timestamp. Results can be filtered by destination ID, status, and time range and support cursor-based pagination.