webhooks
This page is about managing endpoints. For verifying and handling deliveries, see Receiving webhooks.
Delivery is at-least-once: an endpoint you create here can receive the same
event more than once, and your handler must dedupe on event.id. The
at-least-once contract explains why
and shows the pattern.
const endpoint = await boomin.webhooks.endpoints.create({ url: "https://your-app.com/webhooks/boomin", description: "Production", enabledEvents: ["distribution.live", "payout.settled"],});console.log(endpoint.id, endpoint.secret);// we_... whsec_...| Method | Route | Scope |
|---|---|---|
endpoints.create(params, options) | POST /webhook_endpoints | webhooks:write |
endpoints.list(params, options) | GET /webhook_endpoints | webhooks:read |
endpoints.retrieve(id, options) | GET /webhook_endpoints/{id} | webhooks:read |
endpoints.update(id, params, options) | POST /webhook_endpoints/{id} | webhooks:write |
endpoints.rotateSecret(id, params, options) | POST /webhook_endpoints/{id}/rotate_secret | webhooks:write |
endpoints.del(id, options) | DELETE /webhook_endpoints/{id} | webhooks:write |
Endpoints are organization-scoped, not brand-scoped: one endpoint receives every brand’s events in the org, and each event carries its own subject.
The wrapper (raw HTTP only)
Section titled “The wrapper (raw HTTP only)”On the wire, webhook endpoints are the one resource that is not returned
bare — the raw HTTP responses of create, retrieve, update, and
rotate_secret answer:
{ "webhook_endpoint": { "id": "we_...", "object": "webhook_endpoint", ... } }The SDK unwraps that envelope for you — every endpoints.* method resolves
to the bare endpoint object. list is a normal
{ object: "list", data, hasMore } envelope of unwrapped endpoint objects, and
del answers a bare { id, object, deleted: true }.
const endpoint = await boomin.webhooks.endpoints.retrieve("we_...");const { data } = await boomin.webhooks.endpoints.list();The endpoint object
Section titled “The endpoint object”As the SDK returns it (raw HTTP uses snake_case: enabled_events,
created_at):
{ "id": "we_...", "object": "webhook_endpoint", "url": "https://your-app.com/webhooks/boomin", "description": "Production", "enabledEvents": ["distribution.live", "payout.settled"], "status": "enabled", "secret": "whsec_...", "rotatedAt": null, "livemode": true, "createdAt": "2026-08-01T00:00:00.000Z", "updatedAt": "2026-08-01T00:00:00.000Z"}create
Section titled “create”await boomin.webhooks.endpoints.create({ url: "https://your-app.com/webhooks/boomin", // required description: "Production", // optional, ≤ 500 chars enabledEvents: ["distribution.live"], // optional, ≤ 100 entries});Answers 201.
URL rules. https is required. Plain http is accepted only for loopback
(localhost, 127.0.0.1, [::1]) so local development works. Anything else is
invalid_request (400).
Subscriptions. Every entry in enabledEvents must come from the
public event vocabulary; an unknown type is
invalid_event_type (400) naming the offenders. An empty or omitted
enabledEvents subscribes the endpoint to all public types.
No backfill. A new endpoint receives events appended at or after its
creation. To cover the gap, page events.list by
seq.
update
Section titled “update”await boomin.webhooks.endpoints.update("we_...", { enabledEvents: ["distribution.live", "distribution.failed", "payout.settled"], status: "disabled",});Every field is optional; omitted fields are left alone. status accepts
enabled or disabled — disabling stops delivery without destroying the
endpoint or its secret, which is what you want during an incident.
A disabled endpoint exhausts any in-flight deliveries immediately rather than retrying them.
rotateSecret
Section titled “rotateSecret”const endpoint = await boomin.webhooks.endpoints.rotateSecret("we_...");console.log(endpoint.secret); // the NEW secret, shown onceInstalls a fresh whsec_... and keeps the previous secret honored for a
24-hour overlap window. During the window every delivery carries two v1
signature entries — current first, previous second — so a receiver holding
either verifies.
Full zero-downtime procedure: Receiving webhooks.
await boomin.webhooks.endpoints.del("we_...");// { id: "we_...", object: "webhook_endpoint", deleted: true }Hard deletion — the endpoint is gone, along with its secrets. DELETE honors
Idempotency-Key like every other mutation, and the idempotency cache is
consulted before the row lookup, so a replayed delete returns the cached
response instead of 404ing on the endpoint it already removed.
Prefer update({ status: "disabled" }) when you might want the endpoint back.