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
- Send an
Idempotency-Keyon 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
- Treat
201as "accepted," not "sent." The bank send is asynchronous. Never mark a payment "paid" in your system on the201. Wait forpayment.sentand then the bank acknowledgement (executed/rejected). → Core Concepts
- Respect terminal statuses.
executed,rejected, andcancelledare final. Don't build logic that expects them to change; late bank messages can't flip them and neither should you.
- Branch on
code, notmessage. Messages are human text and may change. Codes are stable. → Errors
- Model your error type on the object envelope (
{ error: { code, message, details? } }) for everything except the three documented flat exceptions. → Errors
- Amounts are strings with currency-correct precision.
"1250.00", not1250or1250.0. JPY/ISK take 0 decimals; KWD/BHD take 3. → Validation
- Timestamps you send must be UTC
Z. Offsets are rejected. Prefer to omitcreationDateTimeand let the server stamp it.
- Keep
paymentIdandendToEndIdunique within a batch. Duplicates are hard errors because banks silently de-duplicate on them.
Resilience
- Retry only what's retryable:
429,503, in-progress409, and cautiously5xx. Never blind-retry other4xx. HonourRetry-After. → Errors
- Throttle yourself to the shared bucket. API-key traffic shares one 300-req/60 s bucket per platform, across all your workers and companies. Put a client-side limiter in front of your POSTs. GETs are unlimited.
- 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
- Deduplicate webhooks on the event
id. Delivery is at-least-once; the same event can arrive more than once. Theidis stable across retries.
- Monitor for webhook auto-disable. Five consecutive failures silently disables your endpoint. Watch the delivery log and
X-Delivery-Sequencegaps, and always run a polling backstop so a disabled webhook can't lose you data.
- Ack webhooks fast, process afterward. Return
2xxin well under the 10 s timeout, then do the real work asynchronously keyed on the event id.
- Make retries idempotent end-to-end. Because a crash mid-payment self-heals on the server, your side must also tolerate seeing a
201withIdempotency-Replayed: trueand treat it as success, not a new payment.
Observability
- Log
X-Request-IDon every response. It's how support finds your exact request. Store it alongside your own correlation id.
- Persist the
messageId↔ your-order mapping. Reconciliation (pain.002,camt.053) correlates onmessageId; you'll want to join back to your domain objects.
- 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
- Do all integration work in sandbox first, against the demo bank. The demo bank channel gives you a full payment lifecycle in ~4 seconds, and — by construction, since it's a simulated bank, not a real one — never moves real money. (A sandbox posture alone doesn't guarantee that for a real bank connection; see Going Live for the actual boundary.)
- Don't depend on a live server's
/docssurface in production. The in-appGET /docsandGET /openapi.*return404on a production posture by design. This documentation site (built from a vendored copy of the spec) is safe to depend on instead — it's a separately-hosted static build, not fetched from any one BankConnector deployment at runtime.
- Pin the API version with
BankConnector-Version: YYYY-MM-DDso 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
-
201handled as "accepted"; "paid" is driven by later events - Retry logic covers
429/503/in-progress409and honoursRetry-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=trueon a timer - Webhook auto-disable is monitored and alertable
-
X-Request-IDlogged 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