Integration Modes

View as Markdown

Pick 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).

AtomicFraud onlyPayment only
POST /v1/checkyesyesno (mode_not_allowed)
POST /v1/chargesyesno (mode_not_allowed)yes
Who moves the moneyusyour own acquirerus
Fraud engine at charge timefulln/afull
Decision token issuedyesyes—
Decision token acceptedyesn/ayes
Typical integratormostalready has a PSPrare

Atomic: one call#

Send the buyer's details once; we screen, decide, step up to 3DS if the risk calls for it, and charge.

bash
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 / HTTPWhat to do
succeededDone. Show the receipt.
requires_actionRun 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 errorFix 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

bash
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"
  }'

ip is 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.

json
{
  "object": "fraud_check",
  "status": "clean",
  "action": "allow",
  "requires_3ds": true,
  "reasons": [],
  "risk_score": 12,
  "check_id": "2a5aed3a-…",
  "risk_decision_token": "cmR0MXxtYV8x….9f8e7d…"
}

2. Decide

actionMeaning
denyStop. Do not charge.
reviewCharge, but flag it for your ops team.
allowProceed. If requires_3ds is true, expect a challenge.

3. Charge, echoing the token:

jsonc
{
  "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.