Hosted checkout

The fastest integration: your server creates a Payment, you redirect the customer to the Finscale-hosted page, and a webhook tells you the outcome. Finscale renders the method UI, collects card data, and runs 3-D Secure — none of it touches your servers.

How it works

Customer clicks Pay your checkout page POST /v1/payments your server, sk_test_ / sk_live_ 302 → next_action.url status: requires_action pay.finscale.dev method UI · card fields · 3-D Secure Customer returns your return_url payment.succeeded webhook → fulfil the order server-to-server, signed

Two ideas carry the whole flow: the redirect is presentation — the webhook is truth. The customer coming back to return_url proves nothing about the money; payment.succeeded does.

Create the payment

One server-side call. Pass the method your customer picked — or let the hosted page offer every method enabled on your account for the currency by omitting payment_method. return_url is required for redirect flows:

POST /v1/payments
curl https://api.finscale.dev/v1/payments \
  -H "Authorization: Bearer sk_test_51FinscaleDemo…" \
  -H "Idempotency-Key: idem_ord_9f21_0716" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4900,
    "currency": "EUR",
    "payment_method": "ideal",
    "reference": "ord_9f21_0716",
    "customer": { "email": "anna@example.com" },
    "return_url": "https://shop.example.com/checkout/return"
  }'

Redirect the customer to next_action.url — a plain HTTP 302, a link, or a client-side navigation all work. The URL is single-use and tied to this payment.

Which key does what The create call always uses your secret key, server-side. The publishable key (pk_test_51FinscaleDemo…) has one job: initializing checkout in the browser. It can't read payments, refund, or list anything — see Authentication.

What the hosted page handles

Everything between the redirect and the return is Finscale's problem:

ConcernOn the hosted page
Method UXBank pickers for redirects, QR codes for real-time rails, wallet sheets, card fields — each method's native flow, in the customer's language.
Card dataPAN, expiry, and CVC are collected on pay.finscale.dev, built to PCI DSS Level 1 standards. Your servers stay out of scope for card data entirely — the SAQ-A story in Security & PCI.
3-D SecureChallenges run inside the hosted flow when the issuer requires one. No extra integration — see Cards & 3DS.
Risk screeningCard payments are scored before routing; blocked payments never reach a provider. See Risk engine.
Routing & failoverThe same smart routing as every other integration — hosted checkout adds nothing and removes nothing.

Handle the return

When the customer lands back on return_url, fetch the payment and branch on status — it may still be settling the last hop:

status at returnShow the customerThen
succeededOrder confirmationFulfil when payment.succeeded arrives (it may already have).
processing"Payment in progress"Wait for the webhook — never poll in a loop. Common for bank rails.
requires_actionCheckout again, politelyThe customer abandoned the hosted page. Offer to retry with a fresh redirect.
failedA retry with another methodfailure_code says why — don't echo it verbatim to the customer.

Webhook events

The hosted flow emits the standard payment events — register an endpoint in Webhooks:

  • payment.requires_action — the payment is waiting on the customer (fires at create for redirect methods).
  • payment.processing — customer action complete, transaction submitted to a provider.
  • payment.succeeded — funds captured. Fulfil the order on this event, exactly once (key on event.id).
  • payment.failed — no provider approved it. The payment's failure_code has the normalized reason.
  • payment.risk_review — held for manual review; the payment stays processing until resolved.

Error scenarios

What happensWhat you see
Invalid create request (bad currency, float amount, missing return_url for a redirect method)400 invalid_request_error — nothing was created; fix and retry.
Customer abandons the hosted pageThe payment idles in requires_action, then expires to canceled. No webhook until it does.
Customer's bank or card declinespayment.failed with a normalized failure_code (e.g. insufficient_funds). The hosted page shows the customer a retry.
Same Idempotency-Key, different payload409 idempotency_error — see Idempotency.
Risk screening blocks the paymentpayment.failed, failure_code: payment_blocked. Test it with "amount": 4992.