Integration Modes
View as MarkdownPick how much of the payment you want us to run. The mode lives on your API key, so it is decided once at key creation. No request field can change it, not for you and not for anyone who gets hold of your key.
Before you ship: Integration hard rules (IP, 3DS, idempotency, token, device signals).
| Atomic | Fraud only | Payment only | |
|---|---|---|---|
POST /v1/check | yes | yes | no (mode_not_allowed) |
POST /v1/charges | yes | no (mode_not_allowed) | yes |
| Who moves the money | us | your own acquirer | us |
| Fraud engine at charge time | full | n/a | full |
| Decision token issued | yes | yes | — |
| Decision token accepted | yes | n/a | yes |
| Typical integrator | most | already has a PSP | rare |
Atomic: one call#
Send the buyer's details once; we screen, decide, step up to 3DS if the risk calls for it, and charge.
curl -X POST https://api.worthypayments.com/v1/charges \
-H "X-Api-Key: sk_live_…" \
-H "Idempotency-Key: order_1234" \
-H "Content-Type: application/json" \
-d '{
"amount": 4999,
"currency": "usd",
"card": { "number": "ev:…", "cvv": "ev:…", "exp_month": "12", "exp_year": "2030" },
"customer": { "email": "buyer@example.com" },
"device": { "fingerprint": "fp_9a1c2b" }
}'
Three outcomes. Branch your UI only on these; full JSON shapes are in Charges.
status / HTTP | What to do |
|---|---|
succeeded | Done. Show the receipt. |
requires_action | Run the 3DS challenge, then resubmit the same charge with three_ds.session_id added and nothing else changed. See 3-D Secure. The Payment Element drives this itself (the challenge opens inside the Element's frame, which grows to fit it), so a browser integration never sees this status. |
{ "error": … } (HTTP 402) | Declined. Show a generic decline, never the reason. |
| 400 / other error | Fix the request (missing Idempotency-Key, bad token, validation). See Errors. |
This is the right default. One request, one decision, nothing to keep in sync.
Client IP on charge: not a body field. A charge built on your server comes from your
egress address, which we record as your server's and never score or blocklist, so it cannot
get your backend blocked. To score the charge on the buyer's IP and browser, send the
device_session
the buyer's browser created. The Payment Element confirms from
the browser, so there the peer is the buyer. Details:
Charges → Client IP.
Two-step: you own the decision UI#
Same Atomic key, two calls. Use this when you want to show your own interstitial before charging: your own decline page, your own "verifying your card" screen, your own review queue.
1. Evaluate
curl -X POST https://api.worthypayments.com/v1/check \
-H "X-Api-Key: sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"email": "buyer@example.com",
"fingerprint": "fp_9a1c2b",
"card_bin": "424242",
"last4": "4242",
"amount": 4999,
"currency": "usd",
"ip": "203.0.113.42"
}'
ipis the shopper’s address, not yours. When this call is made from your server, omit it and we score your egress IP instead, which is wrong. See Fraud checks → Client IP.
{
"object": "fraud_check",
"status": "clean",
"action": "allow",
"requires_3ds": true,
"reasons": [],
"risk_score": 12,
"check_id": "2a5aed3a-…",
"risk_decision_token": "cmR0MXxtYV8x….9f8e7d…"
}
2. Decide
action | Meaning |
|---|---|
deny | Stop. Do not charge. |
review | Charge, but flag it for your ops team. |
allow | Proceed. If requires_3ds is true, expect a challenge. |
3. Charge, echoing the token:
{
"amount": 4999,
"currency": "usd",
"card": { "…": "…" },
"device": { "fingerprint": "fp_9a1c2b" },
"risk_decision_token": "cmR0MXxtYV8x….9f8e7d…" // optional, strictly checked
}
See Risk Decision Token for what the token binds and which errors it can produce.
The charge is not "pre-approved" by the check. The full engine runs again at charge time, on the data the charge actually carries. That is deliberate: blocklists, velocity counters and rules change in the minutes between the two calls, and the charge is the moment the money moves.
Fraud only: decision without the money#
For merchants who already have an acquirer and want our screening. /v1/check
and /v1/check/recheck work; /v1/charges returns 403 mode_not_allowed on
every request, so a leaked key cannot move money.
The token is still issued. It is your audit artefact: proof of what we said and when, for a chargeback file.
Payment only: charge without the advisory surface#
/v1/charges works, /v1/check returns 403. Note this does not mean
"skip fraud". The engine runs on every charge in every mode. It means you do not
get the standalone pre-checkout evaluation.
Choosing#
Start with Atomic. Move to two-step when you need control over what the buyer sees between decision and payment. Choose Fraud only when the money already goes somewhere else.
Ask support to change your mode; it is a property of the key, so in practice you are issued a new one.
