sandbox.bankconnector.com API Reference

Best practices: building a resilient client

Audience: 🔌 Integration Client

Each item is a concrete, testable rule.

Correctness

  1. Send an Idempotency-Key on every payment, and freeze the key with the payload. Mint both when you decide to pay, store them, reuse both on every retry. → Idempotency
  1. Treat 201 as "accepted," not "sent." The bank send is asynchronous. Never mark a payment "paid" in your system on the 201. Wait for payment.sent and then the bank acknowledgement (executed / rejected). → Core concepts
  1. Respect terminal statuses. executed, rejected, and cancelled are final. Don't build logic that expects them to change; late bank messages can't flip them and neither should you.
  1. Branch on code, not message. Messages are readable prose and may change. Codes are stable. → Errors
  1. Model your error type on the object envelope ({ error: { code, message, details? } }) on every non-2xx response; the envelope is universal. → Errors
  1. Amounts are strings, always two decimals. "1250.00", not 1250 or 1250.0. You don't adjust decimals per currency; the engine emits each currency's correct precision. It only rejects an amount the currency can't represent (a non-zero sub-unit, e.g. "1000.50" JPY) rather than truncate and change what you're paying. → Validation
  1. Timestamps you send must be UTC Z. Offsets are rejected. Prefer to omit creationDateTime and let the server stamp it.
  1. Keep paymentId and endToEndId unique within a batch. Duplicates are hard errors because banks silently de-duplicate on them.

Resilience

  1. Retry only what's retryable: 429, 503, in-progress 409, and cautiously 5xx. Never blind-retry other 4xx. Honour Retry-After. → Errors
  1. Throttle yourself to both buckets. API-key traffic shares one 300-req/60 s bucket per key, expensive POSTs (including /api/journal/payments/preview) _and_ four GET journal-read routes (/journal/list, /journal/documents, /journal/events, /journal/reconciliation), across all the workers using that key. A poller on those routes spends the same budget a payment submission does. On top of that, a pre-auth per-IP cap of 600/60 s counts every request, GETs included, so a poller behind a NAT'd/shared egress IP can 429 on reads even off that list. Put a client-side limiter in front of your POSTs _and_ your journal polling, keep polling intervals sane, and back off on 429 even for GETs. → Errors
  1. Verify every webhook signature over the raw body, with a constant-time compare, and enforce a timestamp window yourself (it's off by default). Accept any v1= value during secret rotation. → Receiving results
  1. Deduplicate webhooks on the event id. Delivery is at-least-once; the same event can arrive more than once. The id is stable across retries.
  1. Monitor for webhook auto-disable. Five consecutive failures silently disables your endpoint. Watch the delivery log and X-Delivery-Sequence gaps, and always run a polling backstop so a disabled webhook can't lose you data.
  1. Ack webhooks fast, process afterward. Return 2xx in well under the 10 s timeout, then do the real work asynchronously keyed on the event id.
  1. Make retries idempotent end-to-end. Your side must tolerate seeing a 201 with Idempotency-Replayed: true and treat it as success, not a new payment.

Observability

  1. Log X-Request-ID on every response. It's how support finds your exact request. Store it alongside your own correlation id.
  1. Persist the messageId ↔ your-order mapping. Reconciliation (pain.002, camt.053) correlates on messageId; you'll want to join back to your domain objects.
  1. Log validation warnings, don't drop them. A truncation or non-banking-day warning is often the earliest signal of an upstream data problem.

Environment hygiene

  1. Do all integration work in sandbox first, against the demo bank. The demo bank channel runs a full payment lifecycle in about 5 seconds and never moves a real payment. The deployed sandbox also stops every production send, so sandbox.bankconnector.com can't move a real payment (see Going live).
  1. Get the spec from docs.bankconnector.com, not a live server. On a production instance GET /openapi.json and GET /openapi.yaml return 404.
  1. Pin the API version with BankConnector-Version: YYYY-MM-DD so a server-side version bump can't silently change behaviour. It's echoed back on every response.

A pre-launch checklist

Before you call the integration done:

  • Idempotency key sent on every payment, stable across retries
  • 201 handled as "accepted"; "paid" is driven by later events
  • Retry logic covers 429/503/in-progress 409 and honours Retry-After
  • Client-side rate limiter respects the shared per-key bucket
  • Webhook signatures verified over raw body, constant-time, rotation-aware
  • Webhook events deduplicated on id
  • Polling backstop drains uncollectedOnly=true on a timer
  • Webhook auto-disable is monitored and alertable
  • X-Request-ID logged everywhere
  • Error handling branches on code, error type models the object envelope
  • Amounts are currency-correct strings; timestamps are UTC Z
  • Everything validated end-to-end in sandbox before production credentials