Skip to content

Webhooks (developer API)

BUZ sends a thin notification. It deliberately carries no business data — just what happened and where to fetch it:

{
"event": "lead.created",
"organizationId": 1234,
"occurredAt": "2026-08-06T02:15:44.182+00:00",
"resource": {
"type": "lead",
"id": "9f2b...",
"url": "https://api.buzmanager.com/sales/Leads/9f2b..."
}
}

Your endpoint receives this, then calls resource.url with your own BUZ token to get the full record. That’s why the payload is thin: nothing sensitive is sent to an endpoint that hasn’t authenticated, so a leaked or mistyped URL can’t leak customer data.

Event Fires when
lead.created A new lead is created
lead.updated An existing lead is changed
order.invoiced An order is invoiced
payment.received A payment is recorded

Subscribing to anything else is rejected with the list of valid events.

Subscriptions are scoped to your user and the organisation you’re authenticated against. All three calls need a BUZ access token and the Sales / Leads / Access permission.

  1. Create the subscription.

    POST /sales/Webhooks/Subscriptions
    Content-Type: application/json
    { "event": "lead.created", "targetUrl": "https://example.com/hooks/buz" }

    The response contains the subscription id and its signing secret:

    { "id": "3f0c...", "secret": "" }
  2. Store the secret now. It is returned once, at creation, and never shown again — not by the list endpoint, not by support. Lose it and you must create a new subscription.

  3. Verify deliveries using the secret (see below).

GET /sales/Webhooks/Subscriptions

Returns id, event, target URL, active flag and created date for the calling user and organisation. Secrets are never included.

DELETE /sales/Webhooks/Subscriptions/{id}

Deactivates it so deliveries stop.

Every delivery carries an X-BUZ-Signature header:

X-BUZ-Signature: sha256=<lowercase hex>

It is the HMAC-SHA256 of the raw request body, keyed by your subscription secret, prefixed with sha256=.

  1. Read the raw request body — before any JSON parsing or re-serialising. Re-encoding changes the bytes and the signature won’t match.
  2. Compute HMAC-SHA256(rawBody, secret) and hex-encode it in lowercase.
  3. Compare with the header value using a constant-time comparison.
  4. Reject the request if it doesn’t match.
import crypto from 'node:crypto';
function isValid(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header ?? '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
  • Must be http or https — other schemes are rejected when you subscribe.
  • Must be publicly reachable. URLs resolving to private, loopback or reserved IP ranges are rejected at subscribe time and skipped at delivery time.
  • Must answer within 10 seconds. Deliveries time out after that so a slow endpoint can’t hold up someone’s save in BUZ.
  • Should respond quickly and do the work afterwards — acknowledge with 2xx, then process in the background.
  • 410 Gone deactivates the subscription. Return 410 when an endpoint is permanently retired and BUZ stops sending. Never return 410 for a temporary outage.
  • A delivery failure never breaks BUZ. Dispatch failures are swallowed, so a broken endpoint can’t stop a lead from saving.
  • Order isn’t guaranteed. Use occurredAt if sequence matters.
  • Handle repeats. Make your handler idempotent, keyed on resource.id + event + occurredAt.
  1. Verify the signature. Reject if invalid.
  2. Return 2xx immediately — before doing any real work.
  3. Queue the notification internally.
  4. Fetch the full record from resource.url with your own token.
  5. De-duplicate on resource.id + event + occurredAt.
  6. Reconcile periodically to catch anything a failed delivery lost.