Webhooks
Documentation for Bold Bank Webhooks
This documentation covers how to set up and validate Bold Bank webhooks. Webhooks allow you to receive real-time notifications about events in your Bold Bank account.
Webhooks are automated messages sent from Bold Bank to your application when specific events occur.
Webhook Overview
How to set up your webhook URL
Log in to your Bold Bank account, navigate to settings, and provide the URL endpoint where webhook events should be delivered.
Webhook payload structure
Each webhook payload contains these fields:
- event: The event type (for example,
bankTransfer.success). - eventData: The event payload body.
- signature: HMAC-SHA256 signature used for verification.
Common fields inside eventData include:
- sessionId
- productType
- amount
- netAmount
- fee
- commission
- transactionStatus (
SUCCESS,FAILED, orPROCESSING) - transactionReference
- customerReference
- comment
- createdAt
- transactionAccount
- transactionType (
CREDITorDEBIT)
Event-specific objects such as sender, beneficiary, details, or metadata can also appear.
Always inspect eventData.transactionStatus to determine the real state of
a transaction.
Partner integration events (Outlets)
Partners using Outlets receive three additional events in addition to the standard ones.
| Event | When it fires | Purpose |
|---|---|---|
outlet.created | After POST /outlets succeeds | Confirms outlet and wallets are usable. |
walletSweptIntra.successful | After an auto-sweep moves funds from an outlet collection wallet | Reconciliation event with sweepReference. |
internalTransfer.successful | After POST /transfer/internal settles | Confirms internal wallet movement. |
Outlets also receive fundAccount.success and bankTransfer.success / bankTransfer.failed with the same payload shape documented above.
Signature Validation
To verify authenticity, compute HMAC-SHA256 and compare to the payload signature.
Your x-api-key is also the webhook signing secret. Keep it server-side only,
never expose it in client applications, and rotate it immediately if leaked.
Validation Steps
- Extract only the
eventDataobject. - Serialize
eventDataas minified JSON. - Compute HMAC-SHA256 using your
x-api-key. - Compare the hex digest with
signatureusing a constant-time comparison.
Delivery, retries and idempotency
Every webhook is an event with a stable id. The body carries it alongside the fields you already know:
and the request carries these headers:
| Header | Value |
|---|---|
X-Kele-Event-Id | The event id — the same on every attempt of the same event |
X-Kele-Event-Type | Same as event in the body |
X-Kele-Attempt | 1 for the first delivery, then 2, 3, … |
X-Kele-Timestamp | Unix seconds when this attempt was sent |
X-Kele-Signature | t=<timestamp>,v1=<hex HMAC-SHA256> — see Signature v1 below |
User-Agent | Kele-Webhooks/2 |
Delivery is at-least-once and unordered. Answer 2xx within 10 seconds once you have
persisted the event (do your processing afterwards). Anything else — a non-2xx, a timeout, a
connection error, a redirect — is retried on this schedule:
| Attempt | Waits after the previous one |
|---|---|
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 30 min |
| 6 | 1 h |
| 7 | 2 h |
| 8 | 4 h |
| 9 | 8 h |
| 10 | 24 h |
(± 20 % jitter; a Retry-After header on a 429/503 is honoured up to 1 hour.) After the
tenth failure the event is parked as failed and shown in your dashboard, where you can retry
it yourself. If your endpoint fails 20 times in a row, deliveries to it are paused for 60
seconds (doubling to a maximum of 15 minutes while it keeps failing); nothing is dropped — events
queue and resume automatically, and the dashboard shows the pause.
Because retries and pauses exist, you can receive the same eventId more than once (for
example if your server crashed after we sent the request but before we saw your reply).
Deduplicate on eventId: keep the ids you have processed and answer 2xx to a repeat without
processing it again. Do not rely on order — a retried event can arrive after a newer one; the
transaction API (GET /transaction/{transactionReference}) is always the source of truth for
current state.
Signature v1 (X-Kele-Signature)
The v1 signature is computed over the exact bytes of the request body, prefixed with the
send timestamp, so it cannot be affected by how your JSON library re-serialises objects — the
problem the legacy signature field has. Verify it like this:
- Read the raw request body as bytes (before any JSON parsing).
- Split the header on
,→t=<ts>andv1=<hex>. - Compute
HMAC-SHA256(secret, "<ts>." + rawBody)and hex-encode it. - Compare with
<hex>in constant time. - Reject if
|now − ts| > 300 s(replay protection).
The secret is your webhook secret from Settings → Webhooks & IPs (or, until you have
generated one, your x-api-key — the same key the legacy field uses).
The legacy body signature keeps working unchanged and will be kept for at least 12 months
after this notice; new integrations should verify X-Kele-Signature.
Your event log, retries and test events
Everything we tried to deliver to you is visible in the dashboard under Settings → Webhook Events (API businesses only): each event’s status, every attempt with the status code and response your endpoint returned, and the payload. From there you can retry an event and send a test event to your URL. The same is available over the API with your usual credentials:
| Method | Path | Purpose |
|---|---|---|
GET | /webhooks/events/summary?hours=24 | Counts by status, success rate, endpoint health |
GET | /webhooks/events?status=failed&sourceRef=<transactionReference>&limit=25&before=<createdAt> | List events, newest first (before pages) |
GET | /webhooks/events/{eventId} | One event with its attempts and payload |
POST | /webhooks/events/{eventId}/retry | Deliver it again now (same eventId) |
POST | /webhooks/test | Send a signed webhook.test event to your URL |
A retry re-sends the same eventId, so a correctly deduplicating handler will ignore it if
it already has the event — use it when your endpoint was down, not to re-trigger processing.
Best Practices
- Acknowledge fast with HTTP 2xx after persisting the payload. Any non-2xx response, a connection error, or no answer within 10 seconds counts as a failure and is retried on the schedule above.
- Make handlers idempotent — key on
eventId(ortransactionReference/sweepReference), because retries can happen. - Always validate the signature before processing.
- Process asynchronously to keep webhook endpoints responsive.