Webhooks
Receive, verify and deduplicate signed Banklink webhook deliveries.
Banklink sends webhooks when:
| Event | When |
|---|---|
link_request.completed |
A customer completes a persistent link request |
access_request.completed |
A customer completes an access request |
pulse.delivered |
A Pulse runs and delivers transactions |
Each delivery is an HTTPS POST with a JSON body. The body includes event, request_type, the relevant request or Pulse id, your reference, and a transactions array. The full schema is in the API reference.
Headers
Section titled “Headers”| Header | Meaning |
|---|---|
Banklink-Signature |
t=<unix seconds>,v1=<hex>. The v1 value is an HMAC-SHA256 of {t}.{raw body}. |
Banklink-Delivery |
A unique id for this delivery. Use it to ignore duplicates. |
Banklink-Event |
The event name, for example pulse.delivered. |
Verify every delivery
Section titled “Verify every delivery”- Read the raw request body. Don’t parse and re-serialise it first.
- Split the signature header into
tandv1. - Reject the request if
tis more than five minutes old. - Compute HMAC-SHA256 over
t + "." + raw body, using your webhook signing secret. The secret is in Settings → Webhook signing. - Compare with
v1using a constant-time comparison. - Respond with any
2xxstatus once the delivery is stored.
During the 24 hours after a secret rotation, the header carries two v1 values. Accept either.
import crypto from 'node:crypto';
export function verifyBanklinkSignature(rawBody: string, header: string, secret: string) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2))); const t = Number(parts.t); if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); const candidates = header.split(',').filter((p) => p.startsWith('v1=')).map((p) => p.slice(3)); return candidates.some( (v1) => v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)), );}import hmac, hashlib, time
def verify_banklink_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = [p.split("=", 1) for p in header.split(",")] t = next((v for k, v in parts if k == "t"), None) candidates = [v for k, v in parts if k == "v1"] if t is None or not candidates or abs(time.time() - int(t)) > 300: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(c, expected) for c in candidates)Deduplicate
Section titled “Deduplicate”Store Banklink-Delivery and skip any delivery you have already processed. Retries can send the same delivery more than once.
Failures
Section titled “Failures”Any response other than 2xx, and any timeout after 30 seconds, counts as a delivery failure.