BoltHub logoBoltHub
Guides

Webhooks

Subscribe to tenant-scoped events, verify signed deliveries, and manage webhook subscriptions via the bolthub API.

Overview

Webhooks let your backend react to bolthub events in real time. Each subscription points at an HTTPS URL; bolthub POSTs JSON payloads and signs them with HMAC-SHA256 so you can verify authenticity.

All webhook management routes require authentication and are scoped to the tenant (tenantId in the path).

Event types

EventDescription
invoice.settledInvoice was paid
session.createdNew session started
session.expiredSession expired
billing.cycle_closedBilling cycle closed
billing.payment_receivedBilling payment received
billing.suspendedTenant suspended for non-payment
endpoint.health_changedEndpoint health status changed

Subscriptions can also include * to receive all of the above events.

API routes

Base URL in production is https://api.bolthub.ai (use your local API host in development). Paths below are relative to that origin.

MethodPathDescription
GET/tenants/:tenantId/webhooksList webhook subscriptions
POST/tenants/:tenantId/webhooksCreate a webhook
PATCH/tenants/:tenantId/webhooks/:idUpdate a webhook
DELETE/tenants/:tenantId/webhooks/:idDelete a webhook
GET/tenants/:tenantId/webhooks/:id/deliveriesList delivery history for that subscription
POST/tenants/:tenantId/webhooks/deliveries/:deliveryId/retryRetry a failed delivery
POST/tenants/:tenantId/webhooks/:id/testSend a test event

Create webhook

POST /tenants/:tenantId/webhooks

Body:

{
  "url": "https://example.com/webhooks/bolthub",
  "events": ["invoice.settled", "session.created"]
}
  • url must use HTTPS and must not resolve to private or otherwise blocked addresses (SSRF protection).
  • events must contain at least one valid event name (or *).

Response includes the created webhook and a secret (shown only on create). Store the secret securely; you need it to verify signatures.

Update webhook

PATCH /tenants/:tenantId/webhooks/:id

Optional body fields: url, events, isActive. The secret is not rotated by this endpoint.

Delivery history

GET /tenants/:tenantId/webhooks/:id/deliveries

Optional query: limit (default 50, max 100). Returns recent deliveries with status, HTTP status code, attempts, errors, and timestamps.

Test delivery

POST /tenants/:tenantId/webhooks/:id/test

Dispatches a synthetic invoice.settled-shaped delivery so you can confirm connectivity and signature verification. The JSON body uses the same envelope as real events (see below).

Delivery format

bolthub sends an HTTP POST with Content-Type: application/json. Body shape:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "event": "invoice.settled",
  "timestamp": "2025-03-23T12:00:00.000Z",
  "data": { }
}
  • id - unique delivery / event id (also sent as X-Webhook-Id)
  • event - event type
  • timestamp - ISO 8601 time of the payload
  • data - event-specific payload

Verifying signatures

Headers on each delivery:

HeaderDescription
X-Webhook-IdSame as id in the JSON body
X-Webhook-SignatureHex-encoded HMAC-SHA256 of the signing string
X-Webhook-TimestampUnix timestamp in milliseconds (string), same epoch used for signing
X-Webhook-EventSame as event in the JSON body

Signing string: concatenate the timestamp, a single dot (.), and the raw JSON body string exactly as sent (byte-for-byte with the request body):

{timestamp}.{json_body}

Compute HMAC-SHA256 using your webhook secret as the key; the signature bolthub sends is the hex digest of that HMAC.

Example (Node.js):

import crypto from "node:crypto";

function verifyWebhook(secret, rawBody, timestamp, signatureHex) {
  const signed = `${timestamp}.${rawBody}`;
  const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHex));
}

Reject requests with stale timestamps if you want replay protection (not enforced by bolthub).

Retries and URL rules

  • HTTPS only: HTTP URLs are rejected when creating or updating a webhook.
  • Retries: After the first attempt, failed deliveries are retried 3 times with delays of 10 seconds, 1 minute, and 5 minutes (exponential-style backoff between attempts).

Error handling

Your endpoint should return a 2xx status to acknowledge success. Non-2xx responses are treated as failures and follow the retry policy. After retries are exhausted, the delivery is marked failed; use retry on the delivery record to try again manually.