03 — Get started

Quickstart

Take a €49.00 test payment through its full lifecycle in three API calls: create it, check its status, and receive the webhook. No SDK required — everything below is plain curl against https://api.finscale.dev/v1, and every step also runs one click at a time in the API playground.

1. Get your API keys

Every Finscale account starts in test mode with two keys:

KeyPrefixUse
sk_test_51FinscaleDemo…sk_test_Secret key. Server-side only — authenticates every API call in this guide.
pk_test_51FinscaleDemo…pk_test_Publishable key. Safe for browsers; used only with hosted checkout sessions.

You'll find both in the Dashboard under Developers → API keys. Test keys only ever move test money; live keys (sk_live_••••••••••••) come later, after activation. Read Authentication for rotation and key-handling rules.

Keep secret keys server-side

Never embed sk_test_ or sk_live_ keys in mobile apps, browser code, or public repos. Anyone holding the key can move money on your account.

2. Create your first payment

One POST /v1/payments creates the payment and routes it. Here the customer pays €49.00 for order ord_9f21_0716 with iDEAL:

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

Note the two conventions that apply to every write call: money is integer minor units (4900 = €49.00), and the Idempotency-Key header makes the request safe to retry.

The API answers immediately:

201 Created
{
  "id": "pay_8Q2mX4nT1cVb",
  "object": "payment",
  "amount": 4900,
  "currency": "EUR",
  "status": "requires_action",
  "payment_method": "ideal",
  "reference": "ord_9f21_0716",
  "provider": "prov_eu_acq_01",
  "next_action": {
    "type": "redirect",
    "url": "https://pay.finscale.dev/r/8Q2mX4nT"
  },
  "created_at": "2026-07-16T09:24:31Z",
  "livemode": false
}

Two fields matter right now:

  • status: "requires_action" — iDEAL is a redirect method, so the customer must approve the payment at their bank. Send them to next_action.url. In test mode that page shows a simulated bank where you click Approve.
  • provider: "prov_eu_acq_01" — the provider Finscale routed this transaction to. It's an opaque id; your integration never changes based on it.

3. Check the payment status

After the customer approves and lands back on your return_url, fetch the payment to check its state:

GET /v1/payments/pay_8Q2mX4nT1cVb
curl https://api.finscale.dev/v1/payments/pay_8Q2mX4nT1cVb \
  -H "Authorization: Bearer sk_test_51FinscaleDemo…"
200 OK
{
  "id": "pay_8Q2mX4nT1cVb",
  "object": "payment",
  "amount": 4900,
  "currency": "EUR",
  "status": "succeeded",
  "payment_method": "ideal",
  "reference": "ord_9f21_0716",
  "provider": "prov_eu_acq_01",
  "created_at": "2026-07-16T09:24:31Z",
  "livemode": false
}

status: "succeeded" — the €49.00 is captured. The full state machine (requires_action → processing → succeeded | failed) is documented in Payments.

4. Receive the webhook

Polling works, but production integrations should fulfil orders from webhooks. Register an endpoint (Dashboard → Developers → Webhooks, or POST /v1/webhook_endpoints) and Finscale delivers a signed event for every state change:

POST https://shop.example.com/webhooks/finscale
# Headers
Finscale-Signature: t=1784193904,v1=5f8c1e7a9d2b4f6c8e0a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5a7b9d1f3c

# Body
{
  "id": "evt_5s8Y2kLmN0Ta",
  "object": "event",
  "type": "payment.succeeded",
  "created_at": "2026-07-16T09:25:04Z",
  "data": {
    "object": {
      "id": "pay_8Q2mX4nT1cVb",
      "object": "payment",
      "amount": 4900,
      "currency": "EUR",
      "status": "succeeded",
      "reference": "ord_9f21_0716",
      "provider": "prov_eu_acq_01"
    }
  }
}

Verify the Finscale-Signature header (HMAC-SHA256 over t.body with your whsec_FinscaleDemo000… secret) before trusting the payload, respond 2xx quickly, and fulfil order ord_9f21_0716. Signing, retries, and event types are covered in Webhooks.

What just happened

You made one API call; Finscale did the orchestration:

  1. Routing decision

    At POST /v1/payments, the routing engine scored the providers connected to your account against this transaction — currency (EUR), method (iDEAL), cost, and each provider's live authorization rate — and picked prov_eu_acq_01.

  2. Provider abstraction

    Finscale translated your request into that provider's own protocol and normalized the reply back into one payment object. Same request shape, same response shape, whichever provider runs the transaction.

  3. Failover, when needed

    If prov_eu_acq_01 had timed out or soft-declined, Finscale would have retried the same transaction on prov_eu_acq_02 automatically — one API call, two attempts, zero code on your side. Try it yourself with the magic amount 4999 in Testing.

Next steps

  • Testing — test cards, simulated local methods, and forcing a failover with amount 4999.
  • Payments — the full lifecycle and state machine.
  • Routing & providers — how routing scores providers and how per-merchant rules work.
  • Webhooks — signature verification and delivery retries.
  • API reference — every endpoint and field.