Webhooks
BankConnector POSTs signed events to your endpoint as payments move through their lifecycle. This page covers managing and configuring endpoints. For the event envelope, signature verification, retries and the event catalog, see Receiving results. To post into Teams or Slack, see Teams and Slack webhook URLs.
Register an endpoint
curl -X POST https://sandbox.bankconnector.com/api/webhooks \
-H "X-API-Key: $BC_KEY" -H "Content-Type: application/json" \
-H "BankConnector-Version: 2026-10-21" \
-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 /api/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. body.id is undefined.
| Field | Required | Notes |
|---|---|---|
companyId | required | scope: the API key's own company |
url | required | for every transport, including slack, teams and email; omitting it is a 400 url_required. Max 2048 chars. It must be a public HTTPS URL, and a redirect is not followed: it counts as a failed attempt |
eventTypes | optional | array of event names. Omit (or send ["*"]) to receive every event in the catalog; an empty [] is rejected with a 400 |
transport | optional | "https" (default), "slack", "teams", 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 | deliver only matching events; see Filters |
Capture
secreton creation: it's thewhsec_…HMAC signing key and is shown only once. You verify every delivery against it (see Receiving results).
Unknown fields are rejected with 400 validation_failed.
If webhooks are switched off for your organisation, registration answers 403 capability_disabled and no events are sent. They are on by default for the Direct API; ask support to switch them on.
Filters
filterPredicate takes up to four keys, and an event must match every one you set:
bankKeyandcurrencymatch the event's ownbankKeyandcurrency. An event that carries no such field, for exampleapproval.given, never matches and is dropped.minAmountandmaxAmountare in EUR. An event in another currency is compared on its EUR equivalent at the cached reference rate, and one with no rate is delivered whatever its amount.
Only approval.needed, payment.double-debit and statement.unmatched-our-reference carry an amount. On any other event type the amount bounds do nothing. If you list eventTypes and none of them carries an amount, a request with minAmount or maxAmount is refused with 400.
{ "eventTypes": ["approval.needed"], "filterPredicate": { "bankKey": "danske-dk", "minAmount": 1000 } }
Managing endpoints
| Method & path | What it does |
|---|---|
GET /api/webhooks | list your endpoints (metadata only, never the secret) |
GET /api/webhooks/<id> | fetch one endpoint |
DELETE /api/webhooks/<id>?confirm=<id> | remove an endpoint; without ?confirm=<id> the answer is 400 confirmation_required |
POST /api/webhooks/<id>/enable, /disable | switch the endpoint on or off. A disabled endpoint receives nothing, and events sent meanwhile are not kept for later |
POST /api/webhooks/<id>/test | send one signed webhook.test event; see below |
GET /api/webhooks/<id>/deliveries | keyset-paginated delivery attempts (newest first; see Pagination) |
POST /api/webhooks/<id>/deliveries/<deliveryId>/redeliver | replay one delivery as a new attempt |
POST /api/webhooks/<id>/redeliver-failed?since=<iso> | bulk-replay failed deliveries since a timestamp, at most 500 per call; see below |
POST /api/webhooks/<id>/re-enable | clear an auto-disabled endpoint (see below) |
POST /api/webhooks/<id>/rotate-secret | rotate the signing secret; body { "overlapHours": 24 } (default 24, clamped 1–72) |
Managing webhooks needs an admin session or an API key (for the key's own company).
/test is one synchronous POST that answers { "ok": true } or { "ok": false }. It is never retried, writes no delivery-log row and does not count toward auto-disable. For an email endpoint the test is queued instead and appears in the delivery log.
redeliver-failed answers { "count", "nextCursor" }. If nextCursor is set, call again with &cursor=<nextCursor> for the rest. It also replays events that were skipped because the endpoint was auto-disabled. Deliveries are kept for 30 days, so since reaches back no further than that.
Reliability
- Delivery is at-least-once, so deduplicate on the event
id. Success is any HTTP2xx; each attempt times out at 10 s. - 31 delivery attempts, exponential backoff 30 s → 1 m → 2 m → 4 m → 8 m → 16 m → 32 m → 1 h between them, then a flat 1 h wait for each of the 23 attempts after that, about 1443.5 minutes (roughly 24 hours) end to end (±20% jitter). After the 31st attempt the delivery settles
failed; recover it withPOST /api/webhooks/<id>/redeliver-failed?since=<iso>or the single-deliveryredeliverabove. - Circuit breaker: after 5 consecutive failed events an endpoint is auto-disabled and stops receiving until you
POST /api/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 /api/webhooks/<id>/rotate-secret answers { "webhookId", "newSecret", "overlapUntil" }. The new secret is shown once. The old one stays valid until overlapUntil, which is overlapHours from now. During the overlap, deliveries carry multiple v1= signatures (old and new): accept a match against any of them, then cut over to the new secret. See Receiving results.
Transports
- HTTPS (default): a signed
POSTto yoururl. - Slack: set
transport: "slack"and makeurlyour Slack incoming-webhook URL. - Microsoft Teams: set
transport: "teams"and makeurlyour Teams Workflow webhook URL. Each event is posted as a card. Teams and Slack webhook URLs shows how to get either URL. - Email: set
transport: "email"and (optionally)emailTo; events are formatted as email and go to the company's admins whenemailTois omitted.urlis still required, although the email transport doesn't POST to it.