sandbox.bankconnector.com API Reference

Best Practices: Building a Resilient Client

Audience: 🔌 Integration Client

The rest of the guide is how it works. This is how to build against it well. 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. This is the single most important rule for a money-moving integration. → 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 platform (expensive POSTs, including /journal/payments/preview), across all your workers and companies. 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. Put a client-side limiter in front of your POSTs, 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. Because a crash mid-payment self-heals on the server, your side must also 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 gives you a full payment lifecycle in ~5 seconds, and by construction, since it's a simulated bank, never moves real money. The deployed sandbox also hard-stops every production send server-wide, so sandbox.bankconnector.com can't move real money for anyone (see Going Live for the full boundary).
  1. Get the spec from docs.bankconnector.com, not a live server. The docs and OpenAPI spec live only on the docs site (the spec is vendored there at build time). Don't fetch the spec from an API host at runtime: on a production instance GET /openapi.json and GET /openapi.yaml return 404 by design. (GET /docs isn't 404'd anywhere; it 302s to the hosted site.) Treat the docs site as the single source in every environment.
  1. Pin the API version with BankConnector-Version: YYYY-MM-DD so a server-side version bump can't silently change behaviour under you. 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-platform bucket
  • Webhook signatures verified over raw body, constant-time, rotation-aware
  • Webhook events deduplicated on id
  • Polling backstop drains unretrievedOnly=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