Webhooks
Audience: 🔌 Integration Client
BankConnector POSTs signed events to your endpoint as payments move through their lifecycle. This page is the management + configuration reference. For the receiver side (the event envelope, signature verification, retries, and the full event catalog), see Receiving Results.
Register an endpoint
curl -X POST https://sandbox.bankconnector.com/webhooks \
-H "X-API-Key: $BC_KEY" -H "Content-Type: application/json" \
-d '{
"companyId": "comp_…",
"url": "https://erp.example.com/hooks/bankconnector",
"eventTypes": ["payment.sent", "payment.executed", "payment.rejected"]
}'
The 201 body has two top-level keys, endpoint and secret. The endpoint id is at endpoint.id (there is no top-level id):
{
"endpoint": {
"id": "wh_…", // ← the id you pass to every /webhooks/<id>/… route
"url": "https://erp.example.com/hooks/bankconnector",
"eventTypes": ["payment.sent", "payment.executed", "payment.rejected"],
"enabled": true,
// …
},
"secret": "whsec_…", // ← the signing secret, shown ONCE
}
Read the id as body.endpoint.id. If you store body.id you'll persist undefined and be unable to rotate, disable, or delete the endpoint later.
| Field | Required | Notes |
|---|---|---|
companyId | required | scope (platform is inferred from the API key) |
url | required | for every transport, including slack and email — omitting it is a 400 url_required. Max 2048 chars. For the https transport it must be HTTPS (SSRF-guarded, IP-pinned) |
eventTypes | optional | array of event names. Omit to receive all events; an explicitly-empty [] is rejected (a 400, so a typo like events: can't silently subscribe you to everything) |
transport | optional | "https" (default), "slack", or "email" |
emailTo | optional (email) | destination address for the email transport. Omit it and the event is emailed to the company's admins instead |
filterPredicate | optional | { "bankKey": "danske-dk", "minAmount": 1000 }: deliver only matching events |
Capture
secreton creation: it's thewhsec_…HMAC signing key and is shown only once. Store it in your secret manager; you verify every delivery against it (see Receiving Results).
Unknown fields are rejected (400 validation_failed), so a typo can't be silently ignored.
Managing endpoints
| Method & path | What it does |
|---|---|
GET /webhooks | list your endpoints (metadata only, never the secret) |
GET /webhooks/<id> | fetch one endpoint |
DELETE /webhooks/<id> | remove an endpoint |
POST /webhooks/<id>/enable · /disable | pause / resume deliveries |
POST /webhooks/<id>/test | send a webhook.test event through the real signing + delivery path |
GET /webhooks/<id>/deliveries | keyset-paginated delivery attempts (newest first; see Pagination) |
POST /webhooks/<id>/deliveries/<deliveryId>/redeliver | replay one delivery as a new attempt |
POST /webhooks/<id>/redeliver-failed?since=<iso> | bulk-replay every failed delivery since a timestamp |
POST /webhooks/<id>/re-enable | clear an auto-disabled endpoint (see below) |
POST /webhooks/<id>/rotate-secret | rotate the signing secret; body { "overlapHours": 24 } (default 24, clamped 1–72) |
Managing webhooks needs an admin session or a platform API key.
Reliability at a glance
- At-least-once, so deduplicate on the event
id. Success is any HTTP2xx; each attempt times out at 10 s. - 8 delivery attempts with exponential backoff (30 s → 1 h, ±20% jitter).
- Circuit breaker: after 5 consecutive failed events an endpoint is auto-disabled and stops receiving until you
POST /webhooks/<id>/re-enable. Thewebhook.auto-disabledtransition is audit-log only (not itself a webhook), so back webhooks with a periodic poll and watch theX-Delivery-Sequenceheader for gaps.
Secret rotation
POST /webhooks/<id>/rotate-secret issues a new secret while keeping the old one valid for overlapHours. During the overlap, deliveries carry multiple v1= signatures (old + new): accept a match against any of them, then cut over to the new secret. See Receiving Results.
Transports
url is required on every transport — the create route rejects a body without one (400 url_required) before it even looks at transport.
- HTTPS (default): a signed
POSTto yoururl. - Slack: set
transport: "slack"and makeurlyour Slack incoming-webhook URL; events are formatted as Slack messages. - Email: set
transport: "email"and (optionally)emailTo; events are formatted as email, and go to the company's admins whenemailTois omitted. Aurlis still required by the route, even though the email transport doesn't POST to it.