Replay And Freshness¶
The facilitator protects exact-pay-per-request flows in two layers:
- Redis replay markers keyed by both
invoice_idand signed-blob hash - XRPL ledger-window checks in public-gateway mode
Replay Keys¶
Every payment attempt resolves to two replay identifiers:
invoice_idblob_hash
invoice_id comes from the XRPL transaction InvoiceID when present. If the buyer omits it entirely, the facilitator falls back to the first 32 hex characters of the signed transaction blob hash.
That means two requests collide if they reuse either:
- the same XRPL
InvoiceID - the same signed transaction blob
Verify vs Settle¶
POST /verify is a read-only guard. It checks whether either replay key already exists and rejects the payment if so.
POST /settle is the state-changing step:
- reserve both replay keys as
pending - submit the transaction to XRPL
- either convert the reservation to
processedor release it on failure
This split lets middleware reject obvious replays before trying settlement, while still making settlement itself atomic enough to block double submission races.
Redis State¶
Replay markers are stored in Redis as:
pending:<reservation_id>while a settlement is in progressprocessedafter a successful settlement path
Defaults:
- processed TTL:
REPLAY_PROCESSED_TTL_SECONDS, default604800seconds - pending TTL:
max(VALIDATION_TIMEOUT + 60, 300)
If either replay key already exists, the facilitator rejects the payment with:
Transaction already processed (replay attack)
Settlement Mode Effects¶
validated¶
In validated mode, the facilitator:
- submits the transaction
- polls XRPL for up to
VALIDATION_TIMEOUTseconds - waits for
tx.result.validated - checks the delivered amount against the required exact amount
- returns
status="validated"
If validation never arrives in time, the pending reservation is released and settlement fails.
optimistic¶
In optimistic mode, the facilitator:
- submits the transaction
- marks the replay reservation processed immediately
- returns
status="submitted"
This mode lowers latency, but it shifts more validation responsibility to the surrounding system.
Freshness Rules In redis_gateways Mode¶
When GATEWAY_AUTH_MODE=redis_gateways, the facilitator additionally requires bounded XRPL timing:
- the signed transaction must include
LastLedgerSequence LastLedgerSequencemust be greater than the latest validated ledgerLastLedgerSequencemust not exceedcurrent_validated_ledger + MAX_PAYMENT_LEDGER_WINDOW
If any of those checks fail, the facilitator rejects the payment before settlement.
This is why public-gateway mode is stricter than single_token: it assumes third-party sellers may retry or relay traffic, so the facilitator enforces a narrow ledger window for safer exact-payment handling.
Practical Guidance¶
- generate a fresh signed transaction per paid request
- use
invoice_id_factoryon the client when you want stable cross-service correlation - leave XRPL autofill enabled, or set
LastLedgerSequenceyourself, when usingredis_gateways - prefer
validatedsettlement for internet-facing deployments
For the on-the-wire request/response format, continue to Header Contract.