Production integration guide
Everything on this page assumes the basic flow works (see Backend checkout integration) and covers what changes when real money and real traffic are involved.
Recommended architecture#
Checkout page ──▶ Merchant backend ──▶ PayInference Decision API (sync, tight budget)
│
├──▶ PSP execution (your existing stack)
│
└──▶ Outcome reporting (async, queued)
- Call PayInference from the backend service that owns payment execution, so the decision and the execution share a process and a log line.
- Keep outcome reporting asynchronous: an in-process queue or your job system, with retries. It is idempotent per
decision_id. - The browser never calls PayInference and never sees an API key.
Timeouts and fallbacks#
Give the decision call a hard budget and a plan for when it is exceeded. The server-side design budget is 50 ms; your client budget adds network time on top.
const payinference = new PayInferenceClient({
apiKey: process.env.PAYINFERENCE_API_KEY!,
timeoutMs: 150, // hard budget for decide(); tune to your network
reportingTimeoutMs: 2000, // outcomes, health reads
retries: 2, // network-error retries for reporting calls only
fallback: { provider: 'stripe', fallback_provider: 'adyen' },
});
With fallback configured, a timeout or network failure makes decide() return a locally built decision (action: "use_default_route", reason_codes including SDK_FALLBACK_USED, and sdk_fallback: true) instead of throwing. Checkout never waits on an outage.
Choose your own safe default; there is no universal one. If PayInference is unavailable, follow a default based on your own policy: for low-risk payments, continuing through your default provider is usually right; for high-risk payments, your policy may instead require a step-up, a pending state in your order system, or refusing the attempt. Encode that choice explicitly, whether via the SDK fallback option, or your own handler for PayInferenceTimeoutError and PayInferenceNetworkError.
Idempotency#
- Send an
Idempotency-Keyper attempt on decision calls so client retries never act on two different decisions, deduplicate checkout submissions onorder_id, and use your PSP's idempotency keys for the charge itself. - Outcome calls are idempotent per
decision_id; retry them freely.
Details: Idempotency and retries.
Retries and circuit breaking#
- Never retry HTTP error responses from PayInference; they are answers.
- Retry network errors for reporting calls only. For decisions, prefer the fallback over retries: two timeout windows is checkout time you do not have.
- Wrap the decision call in your standard circuit breaker. When the circuit is open, go straight to your safe default and skip the call entirely; the SDK fallback then never even waits for the timeout. Reset probing on your breaker's normal schedule.
Logging#
Log, at minimum, per attempt:
order_id,decision_id,action,route.primary_providerreason_codes(they are safe to log; that is what they are for)decision_latency_msand your measured client-side latency- Whether the SDK fallback was used (
sdk_fallback) - The
x-request-idresponse header on failures
Never log the request payload wholesale into systems with weaker access control; it contains no card data by construction, but amounts and countries are still business data.
Outcome reporting at scale#
- Queue outcome reports and flush asynchronously with backoff. Delivery matters more than immediacy.
- Report failures as diligently as successes; failure outcomes drive provider health and retry intelligence.
- Map PSP response codes to the canonical outcome values once, in one module, and pass the raw code in
provider_reason_code.
Testing before go-live#
- Integrate against a local instance (
pnpm dev, seeded demo data) or your deployment's test environment with api_test_…key. Test-mode decisions are marked"mode": "test"and kept separate in analytics. - Simulate policies before publishing:
POST /v1/policies/simulate. - Load-test the decision path with
pnpm benchmarkagainst your own deployment to validate the latency budget from your network position.
See Testing and local sandbox.
Operational monitoring#
Watch, and alert on:
| Signal | Why |
|---|---|
| Client-side decide latency p95 and timeout rate | Your checkout budget; sustained timeouts mean the fallback is carrying traffic |
Fallback usage rate (SDK_FALLBACK_USED in your logs) |
Nonzero is fine transiently; sustained means an availability problem |
429 rate |
Traffic outgrew the configured per-key limit |
Instruction mix (action distribution over time) |
A sudden spike in block or step_up usually means a policy or risk change |
| Outcome approval rate per provider | The end-to-end measure of routing quality |
GET /ready |
Liveness of a self-hosted deployment's dependencies |
Security checklist#
- API keys live in a secret manager, never in source, images, or frontend bundles
- Separate keys per service and environment; least-privilege scopes (
decision:write+outcome:writefor checkout) - Test keys (
pi_test_…) in staging, live keys (pi_live_…) in production, never mixed - Key rotation procedure exercised (create new, deploy, revoke old)
- No card data, CVV, email, or address in any field you send; rely on the schema rejection but do not depend on it
- Outcome and decision logs on your side treated as business-sensitive
-
x-request-idcaptured for support escalation
Go-live checklist#
- Timeout and fallback behavior tested by blackholing PayInference in staging
- Every instruction handled, including
block,step_up, and unknown-action default - Outcome reporting verified end to end (check the decision log shows outcomes)
- Policy published and simulated against representative transactions
- Dashboards and alerts from the monitoring table above in place