Skip to content
Banklink API

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.

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.
  1. Read the raw request body. Don’t parse and re-serialise it first.
  2. Split the signature header into t and v1.
  3. Reject the request if t is more than five minutes old.
  4. Compute HMAC-SHA256 over t + "." + raw body, using your webhook signing secret. The secret is in Settings → Webhook signing.
  5. Compare with v1 using a constant-time comparison.
  6. Respond with any 2xx status 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)

Store Banklink-Delivery and skip any delivery you have already processed. Retries can send the same delivery more than once.

Any response other than 2xx, and any timeout after 30 seconds, counts as a delivery failure.