Idempotency
Every mutating request (initiate, process, cancel) requires an
X-Idempotency header. If you send the same key twice, the plugin returns the original response without creating a duplicate operation.
Rules:
- Use a UUID v4 or a unique business identifier (e.g. your internal order ID)
- Maximum length: 255 characters
- The plugin scopes each key to the effective organization. The same key from two organizations counts as two distinct requests.
- The plugin returns a cached response for the configured idempotency window (
IDEMPOTENCY_RETRY_WINDOW_SEC, default 300 seconds) - A replayed response is byte-identical to the original: same status code, same body. The response has no header to mark a replay, so design your client to stay safe in either case.
Duplicate detection
Beyond idempotency keys, the plugin detects content-based duplicates. It builds a fingerprint from:senderAccountId- recipient details (ISPB, branch, account, holder document)
- amount
- purpose
idempotency.duplicate_guard_ttl_seconds. The organization is not part of the fingerprint. Tenant isolation comes from the Redis key prefix. The plugin rejects the request with 409 BTF-0012 if the client already submitted a matching transfer inside the window.
This catches cases where the client sends the same transfer with a different idempotency key. One example is a retry after a timeout, when the client did not receive the original response.
Retry strategy
Use exponential backoff for transient errors. Do not retry every error.
Recommended backoff schedule for 5xx/503: 0s, 5s, 25s, 60s, 120s (5 attempts total).
When JD SPB is unavailable, the response is
HTTP 503. The error.code field then carries the raw JD vendor code — for example, TRANSPORT for transport failures or ACE95 for timeouts. The plugin does not wrap JD-chain failures in a BTF- code. Flag the transfer for manual reconciliation after the retries run out. Do not retry without limit. The JD SPB network has defined operating hours.State handling
TED OUT state machine
Transfers follow a strict progression. You cannot cancel a transfer after it leavesCREATED or PENDING.
Initiation state machine
The initiate endpoint creates aPaymentInitiation entity. This entity has its own lifecycle before the plugin creates a Transfer.
TED IN state machine
P2P state machine
P2P does not have aPENDING state. Settlement is atomic and instant.
Polling vs. webhooks
Prefer webhooks for real-time status. If you have not configured webhooks yet, pollGET /v1/transfers/{transferId}. Use a maximum of 10 attempts with the same backoff schedule as retries. Flag the transfer for manual review after 10 minutes with no terminal state (COMPLETED, REJECTED, FAILED, CANCELLED).
See Get Transfer and Webhooks.
Webhook integration
For event payload schemas and the full list of events, see Webhooks.
Signature validation
Every webhook request includes headers your endpoint uses to verify authenticity:X-Webhook-Signature— versioned HMAC-SHA256 signature in the formv1,sha256=<hex>X-Webhook-Timestamp— Unix timestamp in seconds (UTC) when the plugin built the requestX-Webhook-Event— the event type (for example,transfer.completed). This header is not part of the signature.
v1:, the timestamp value from X-Webhook-Timestamp, one ASCII dot (.), then the raw request body bytes. Use the body bytes exactly as they arrive on the wire. Do not parse or re-encode them first.
To validate:
- Read
X-Webhook-SignatureandX-Webhook-Timestampfrom the request headers. - Build the signed string:
"v1:" + timestamp + "." + rawBody. - Compute
HMAC-SHA256over the signed string with yourWEBHOOK_SIGNING_SECRET, then hex-encode the result. - Prepend
v1,sha256=, then compare againstX-Webhook-Signaturewith a constant-time equality function. - Reject the request if the timestamp is outside an acceptable freshness window (a 5-minute tolerance is typical) to prevent replay.
X-Webhook-Signature and X-Webhook-Timestamp, the plugin sets only X-Webhook-Event (the event type). It does not send X-Webhook-Event-Type, X-Webhook-Routing-Key, or X-Webhook-Delivery-Attempt.
JavaScript
JavaScript
Python
Python
Go
Go
Idempotent webhook processing
Your endpoint may receive the same event more than once (at-least-once delivery). UsetransferId + event as a composite key to deduplicate.
Error handling patterns
Map API error codes to user-facing actions. See the full error list for all codes.
Error responses follow this structure:
Go-live checklist
Before enabling the integration in production:
- Send
X-Idempotencyon every initiate, process, and cancel request - Retry logic implemented with exponential backoff for 5xx/503 errors
- Webhook endpoint deployed and returning
200within 5 seconds - Signature validation active on the webhook endpoint
- Webhook event deduplication implemented using
transferId + event - Operating hours validated client-side before calling initiate (reduces unnecessary 422s)
- Both
transferIdandconfirmationNumberstored for reconciliation - Terminal states (
COMPLETED,REJECTED,FAILED,CANCELLED) handled in UI - Initiation expiry (24h) handled — prompt the user to restart when the window passes
- Service readiness monitored in your alerting system for BYOC deployments
- Redis reachable and monitored — the service rejects requests when Redis is down
-
PLUGIN_AUTH_ENABLED=trueconfigured in production, with a validPLUGIN_AUTH_ADDRESS(HTTPS)

