Getting Started

Set up an account, create API keys, switch between test and live.

Welcome to spuke

Take card, wallet, bank and BNPL payments with one API — no PSP account required.

Welcome to spuke

spuke is a payments platform. You create a merchant account, we handle the PSP relationships (cards, wallets, SEPA, Klarna, iDEAL, and more), and you get one REST API to charge customers, run subscriptions, issue refunds and receive payouts to your bank.

This section gets you from zero to your first live payment.

What you can do with the API

Capability Endpoint Docs
One-off payment POST /v1/checkout/sessions Checkout Sessions
Recurring billing POST /v1/subscriptions Subscriptions
Invoicing POST /v1/invoices Invoices
Catalog POST /v1/products · POST /v1/prices Products
Refunds POST /v1/refunds Refunds
Realtime updates Webhooks How webhooks work

Everything is JSON over HTTPS. Base URL: https://api.spuke.com/v1.

Quickstart (5 minutes)

1. Onboard your merchant account. Sign in to the dashboard, complete the 15-step onboarding, and wait for approval. You must be active before live keys work.

2. Grab an API key. Dashboard → DevelopersAPI keys. You'll get two:

  • sk_test_… — sandbox, no real money, no payout.
  • sk_live_… — real money.

Treat these like passwords. See API security best practices.

3. Make your first charge.

curl -X POST https://api.spuke.com/v1/checkout/sessions \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_0001" \
  -d '{
    "amount": 2500,
    "currency": "eur",
    "customer_name":  "Jane Doe",
    "customer_email": "jane@example.com",
    "success_url": "https://example.com/thanks",
    "cancel_url":  "https://example.com/cart"
  }'

Redirect the buyer to the returned checkout_url. Use test card 4242 4242 4242 4242 with any future expiry and any CVC.

4. Receive the webhook. Add an endpoint at Dashboard → Developers → Webhooks and listen for checkout.session.completed. Always verify the signature — instructions here.

Core concepts

  • Merchant account. Your business on spuke. Owns your keys, products, transactions, payouts.
  • Session / Subscription / Invoice. Three ways to collect money. Pick by use case.
  • client_secret. Short-lived token used by the browser to confirm a payment. Never trust it as proof of payment — details.
  • Fees. Per-plan, per-method, calculated in EUR and deducted per transaction. See Fees.
  • Payouts. Automatic, on your configured schedule (daily/weekly), to your verified bank account.

Environments

Mode Keys Money Payouts
Test sk_test_… / pk_test_… No No
Live sk_live_… / pk_live_… Yes Yes

Both modes share the same base URL and endpoints. The key alone decides which environment you hit.

Idempotency, errors, rate limits

  • Send Idempotency-Key: <your-key> on every mutating request. Safe retries, no duplicate charges.
  • Errors follow a stable shape: { "error": { "type": "…", "message": "…", "param": "…" } }. See Error reference.
  • Default limits: 60 req/min per key for reads, 20 req/min for writes. 429 rate_limited includes a Retry-After header.

Where to next

API keys & authentication

Create keys, keep them safe, rotate them

API keys

Create a key

Open Dashboard → Developers → API keys and click Create key. Pick a name, a mode (Test or Live), and copy the secret. The secret is shown once — spuke stores only a SHA-256 hash. If you lose it, revoke and create a new one.

Keys look like:

sk_live_ABCDEfghi23jkLmnOPqrSTUv...   ← live
sk_test_ABCDEfghi23jkLmnOPqrSTUv...   ← test

Authenticate a request

Send the secret as a Bearer token in the Authorization header:

POST /v1/checkout/sessions HTTP/1.1
Host: api.spuke.com
Authorization: Bearer sk_test_ABCDEfghi23jkLmnOPqrSTUv...
Content-Type: application/json

Never call the API from the browser — the secret would leak to visitors. Always proxy through your own backend.

Scopes

Keys are unscoped by default and grant full merchant access (checkout:write, checkout:read, refunds:write, invoices:write). Per-scope keys are on the roadmap.

Rotation

Rotating a key is a two-step revoke:

  1. Create a new key, deploy it, verify traffic uses it.
  2. Revoke the old key in the dashboard (revoked_at is set immediately, all subsequent requests receive 401).

Idempotency

Every mutating request accepts an Idempotency-Key header. spuke stores the first response for 24 hours and returns the exact same status and body when the same key is sent again with the same payload. Reusing the key with a different payload returns 400 invalid_request (Idempotency-Key reused with different payload).

Idempotency-Key: order_9781_attempt_1

Currencies & amount format

Supported currencies, minor units, zero-decimal rules, min/max.

Currencies & amount format

All amount fields in the spuke API are integers in the smallest currency unit (minor units), following the ISO 4217 exponent for that currency.

Amount format

Currency type Example 10.00 → integer
2-decimal (most currencies) EUR, USD, GBP, CHF, CAD, AUD, SEK, NOK, DKK, PLN, CZK, RON, HUF, BGN 1000
0-decimal (zero-decimal) JPY, KRW, VND, CLP, ISK, HUF (as 0-dec via spuke) 10
3-decimal not currently supported

Send integers only. 10.5 or "10.00" are rejected with invalid_request.

Supported currencies

Currencies accepted on POST /v1/checkout/sessions:

eur, usd, gbp, chf, sek, nok, dkk, pln, czk, ron, bgn, huf, cad, aud, nzd, jpy, sgd, hkd.

Always lowercase 3-letter ISO. Currencies outside this list return 400 invalid_request (currency_unsupported).

Payment method coverage

Not every method supports every currency. spuke automatically hides methods at checkout that the currency does not support (e.g. Klarna is EU-only, iDEAL is EUR-only, Bancontact is EUR-only).

Minimum and maximum

  • Minimum: 2.50 EUR equivalent (e.g. 250 in EUR/USD/CHF, 300 in JPY). Below this: 400 amount_too_small.
  • Maximum per charge: 999,999.99 in the transaction currency. Above this: 400 amount_too_large.

See Fees, currency & minimum amounts for the reasoning behind the minimum.

Payout currency

Merchants are paid out in their onboarding currency (usually EUR or CHF). Charges in other currencies are converted at the daily FX rate with a +2.00% FX surcharge.

Fees, currency & minimum amounts

How spuke calculates fees, converts currencies and why there is a minimum charge.

Fees, currency & minimum amounts

spuke charges fees in a predictable way — no matter which currency your customer pays in. This page explains exactly how it works.

1. Fees are always denominated in EUR

Every fee in your plan (Free / Starter / Business / Plus) and every per-method fee shown on the pricing and compare pages is defined in EUR.

  • The percentage part (e.g. 2.5%) applies to the transaction amount converted to EUR.
  • The fixed part (e.g. €0.25) is an EUR amount.
  • The final fee is charged in the transaction currency, converted at the live FX rate at the moment the payment is captured.

Example: a customer pays CHF 40.00 on the Business plan (3.9% + €0.30).

  1. CHF 40.00 → EUR ≈ 41.20 (live FX)
  2. Fee in EUR = 41.20 × 3.9% + 0.30 = €1.91
  3. Fee back to CHF ≈ CHF 1.85 — this is what appears on the charge.

You always see both values in your dashboard (wallet, transactions, ledger).

2. Why the fee ledger sometimes shows a "top-up"

At checkout creation we don't yet know which card region the customer will use, so we charge the base rate. Once the payment is captured we know the real card region (EU / UK / intl) and compare the real fee to what was already deducted:

  • Correct: no ledger entry.
  • Under-collected: a fee_topup entry is created and added to the next transaction.
  • Over-collected: a refund entry balances it out.

This guarantees the merchant always pays exactly the plan's rate — no more, no less.

3. Minimum charge amount: 2.50 EUR

You can only create invoices, products and payment links with a total of at least 2.50 EUR (or the equivalent in the transaction currency).

Why? PSP fees + spuke fees always include a fixed component (e.g. €0.25 – €0.95 per transaction). On a €1.00 charge those fixed costs would exceed the amount and result in a loss — for you and for us.

The 2.50 minimum applies to:

  • Invoice totals (line items + tax)
  • Product prices (one-time and recurring)
  • API checkout_sessions — smaller amounts are rejected with amount_too_small

For subscriptions with trial periods the minimum applies to each billing cycle amount, not to the trial.

4. Payout currency & FX conversion

Payouts are made in your merchant currency (usually EUR or CHF). If a customer pays in another currency:

  • The gross amount is converted to your payout currency at the daily FX rate.
  • A +2.00% currency-conversion surcharge is applied on top of the standard fee.
  • The full breakdown is visible on the transaction detail sheet.

5. Full fee summary (per transaction)

Component When Amount
Plan percentage Every transaction e.g. 2.5% (plan-dependent)
Plan fixed Every transaction e.g. €0.25 (plan-dependent)
International card surcharge Card issued outside merchant country +1.50%
FX surcharge Charge currency ≠ payout currency +2.00%
Chargeback Per dispute case €15.00
Payout Per bank payout included on all paid plans

All values are defined in EUR and converted per transaction. Surcharges apply on top of the plan rate and only when the trigger condition is met.

6. TL;DR

  • Everything is priced in EUR.
  • Fees are converted to the transaction currency at capture time.
  • Minimum 2.50 EUR per invoice, product or checkout — otherwise fees can't be covered.
  • The fee ledger self-corrects so your effective rate always matches your plan.

API security best practices

How to protect your secret keys, webhooks and integration.

API security best practices

Never expose secret keys

  • sk_live_… and sk_test_… grant full merchant access. Never ship them in browser code, mobile apps, git repos, logs, error reports, or client-side analytics.
  • Always call the spuke API from your server. From the browser, only ever use the client_secret returned by POST /v1/checkout/sessions.
  • Rotate keys immediately if you suspect a leak (Dashboard → Developers → Revoke).

Separate test and live

  • sk_test_… keys only work in test mode and never move real money. Use them for CI, staging and local development.
  • Live keys should be stored in your production secret store (AWS Secrets Manager, Vault, GitHub Actions Secrets, etc.) — never in .env files committed to git.

Enforce TLS

  • The API only accepts requests over HTTPS (TLS 1.2+). Plaintext HTTP is rejected.
  • Verify TLS certificates in your HTTP client. Disabling verification defeats the point.

Always use Idempotency-Keys

  • Send an Idempotency-Key header on every POST that creates money-moving objects.
  • Use a stable value per business action (e.g. order_12345) — not a random UUID per retry.
  • See Checkout Sessions → Idempotency.

Verify webhook signatures

  • Never trust a webhook body without verifying the Spuke-Signature header with your whsec_… secret.
  • Reject signatures older than 5 minutes (replay protection).
  • See Webhooks → Verify signatures.

Rate limits

  • Default: 100 requests / second per merchant, burst 200.
  • Exceeding returns 429 rate_limited with a Retry-After header. Implement exponential backoff.

Principle of least privilege

  • Create separate keys per environment / service and revoke unused ones.
  • Per-scope keys (checkout:write only, refunds:write only, …) are on the roadmap.

Incident response

If a key is leaked:

  1. Revoke it in the Dashboard immediately — all requests using it start returning 401.
  2. Create a new key and deploy it.
  3. Review the Developers → Request log for suspicious traffic.
  4. Contact support@spuke.com if funds were moved that you did not authorise.