API reference/Webhooks

Core resource

Webhooks

Register an HTTPS endpoint to receive event notifications. Every delivery is signed with HMAC-SHA256 in a Holdyn-Signature header, in the Stripe-compatible t=<unix-seconds>,v1=<hex> format.

Register an endpoint

POST/api/v1/b2b/webhook-endpoints

Dashboard session required. The signing_secret is returned once at creation — store it before you leave the response.

Request bodyjson
{
  "url": "https://example.com/webhooks/holdyn-b2b",
  "description": "Prod listener",
  "subscribed_events": ["*"]
}

Signature verification

Compute HMAC-SHA256 over ${t}.${rawBody} with your endpoint's signing secret and compare to the v1 component of the header.

Node.js verificationjavascript
const crypto = require('crypto');

function verifyHoldynSignature(rawBody, signatureHeader, signingSecret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 || ''),
  );
}
Always use a constant-time comparison. A plain === string check is vulnerable to timing attacks. crypto.timingSafeEqual in Node and equivalents in other runtimes prevent leakage of the expected HMAC byte-by-byte.

Retry policy

AttemptTimingOutcome on failure
1Immediate, synchronous with the eventSchedule attempt 2 at +60 s
2+60 s after attempt 1Schedule attempt 3 at +5 min
3+5 min after attempt 2Mark delivery exhausted, no further retries

Receivers must respond with a 2XX within 10 seconds. Any other status code or timeout counts as a failure. Acknowledge quickly and defer work to a background queue on your side.

Inspect deliveries

GET/api/v1/b2b/webhook-deliveries

Cursor pagination; filter by status and/or webhook_endpoint_id. Each delivery row carries the request body, response status, response body (truncated), and timing.