Webhook management
Create endpoints, store signing secrets, inspect attempts, and replay failed deliveries.
The Server SDK manages webhook endpoints. Your receiver uses the one-time signing secret to verify every delivery before processing it.
Choose event types
WebhookEvent is generated from the public contract. Its named values prevent misspelled event strings and update with the SDK when the contract changes.
import { WebhookEvent } from "@cipay/client-sdk/api";
const selectedEvents = [
WebhookEvent.CheckoutCompleted,
WebhookEvent.OrderPaid,
] as const;
Discover the event catalog
eventCatalog() asks Cipay which events the current API supports. Use it to build a dynamic endpoint-configuration screen or inspect the current schema version. It returns organization and commerce event arrays; it does not create an endpoint or subscribe to anything.
const catalog = await cipay.webhooks.eventCatalog();
console.info({
schemaVersion: catalog.schemaVersion,
commerceEvents: catalog.commerce,
organizationEvents: catalog.organization,
});
Organization endpoints accept WebhookEvent.Member* values. Sandbox and production endpoints accept commerce values from catalog.commerce. The event catalog reference documents the complete response.
Create an endpoint
The selected scope controls which environment can emit deliveries. Cipay returns the signing secret only in this creation response.
import {
WebhookEvent,
createIdempotencyKey,
} from "@cipay/client-sdk/api";
const created = await cipay.webhooks.create(
{
name: "Storefront events",
url: "https://shop.example.test/api/cipay/webhooks",
scope: "sandbox",
selectedEventTypes: [
WebhookEvent.CheckoutCompleted,
WebhookEvent.OrderPaid,
],
},
{ idempotencyKey: createIdempotencyKey("webhook") },
);
Store the one-time secret
Move the returned secret directly into your server secret manager. This is application infrastructure, not another Cipay API call.
await secrets.store("CIPAY_WEBHOOK_SECRET", created.secret);
Verify received events
Assign the stored value to CIPAY_WEBHOOK_SECRET in the receiving service. Verify the exact raw request bytes before parsing JSON.
import { verifyWebhook } from "@cipay/client-sdk/api";
// Parsing and re-encoding the body would change the signed bytes.
const rawBody = new Uint8Array(await request.arrayBuffer());
const event = verifyWebhook({
rawBody,
headers: request.headers,
secret: process.env.CIPAY_WEBHOOK_SECRET!,
});
console.info({ eventId: event.id, eventType: event.type });
The receiver guide provides complete Next.js, Hono, Astro, and Express examples with durable deduplication.
Retrieve an endpoint before editing
Versioned webhook writes require the latest revision.
const endpoint = await cipay.webhooks.retrieve(endpointId);
console.info({
id: endpoint.id,
status: endpoint.status,
revision: endpoint.revision,
});
Edit an endpoint
import {
WebhookEvent,
createIdempotencyKey,
} from "@cipay/client-sdk/api";
const updated = await cipay.webhooks.edit(
endpoint.id,
{
selectedEventTypes: [
WebhookEvent.CheckoutCompleted,
WebhookEvent.OrderPaid,
WebhookEvent.SubscriptionRenewed,
],
},
{
revision: endpoint.revision,
idempotencyKey: createIdempotencyKey("webhook-edit"),
},
);
Disable an endpoint
Disable stops new deliveries without deleting endpoint history.
await cipay.webhooks.disable(endpoint.id, {
idempotencyKey: createIdempotencyKey("webhook-disable"),
});
Rotate the signing secret
Rotation returns a new one-time secret. Update the receiver, verify a delivery with the new value, and then retire the previous secret according to your deployment process.
const rotated = await cipay.webhooks.rotateSecret(endpoint.id, {
idempotencyKey: createIdempotencyKey("webhook-rotate"),
});
await secrets.store("CIPAY_WEBHOOK_SECRET", rotated.secret);
List deliveries
const deliveries = await cipay.webhooks.deliveries.list(endpointId, {
pageSize: 20,
});
console.info({
returned: deliveries.items.length,
hasNextPage: deliveries.nextCursor !== null,
});
Retrieve one delivery
Use the delivery ID selected by your tooling; do not assume the first list item exists.
const delivery = await cipay.webhooks.deliveries.retrieve(
endpointId,
deliveryId,
);
console.info({
id: delivery.delivery.id,
status: delivery.delivery.status,
attempts: delivery.attempts.length,
});
Replay a failed delivery
Fix the receiver before replaying. The replay contains the same business event, so your receiver must deduplicate by the signed event ID.
import { createIdempotencyKey } from "@cipay/client-sdk/api";
if (delivery.delivery.status === "failed") {
await cipay.webhooks.deliveries.replay(endpointId, delivery.delivery.id, {
idempotencyKey: createIdempotencyKey("webhook-replay"),
});
}