Developer reference v1

The topropay API.
Built for engineers who route payments.

One REST API authorises, captures, refunds, and reports across every connector you wire in. Stable URLs. Predictable JSON. Signed webhooks. Idempotent writes.

Jump to… payments, webhooks, routing ⌘K
  • API status: operational
  • HMAC-signed webhooks
  • Idempotency on every write
  • PCI DSS Level 2

Overview

#One API for every connector

A JSON REST API for payment orchestration. Author one request shape; we route across the acquirer, PSP and local-method connectors you have configured.

  • Payments POSTGET

    /v1/payments

    Authorise, capture, void and inspect payments across every connector.

  • Refunds POSTGET

    /v1/refunds

    Full and partial refunds routed back through the original acquirer.

  • Customers POSTGETDELETE

    /v1/customers

    Network-tokenised card storage. Re-use across any acquirer route.

  • Routing GETPOST

    /v1/routes

    Inspect the routing decision and read live approval scores per connector.

  • Webhooks POSTGETDELETE

    /v1/webhooks

    Subscribe a URL to signed event deliveries — one feed for every connector.

  • Reports GET

    /v1/reports

    Settlement, reconciliation, and approval-rate exports — schedule into your warehouse.

Quick start

#From key to first payment in 5 minutes

Every snippet runs against the sandbox without acquirer setup; the sandbox simulates approvals, soft declines and cascades for you.

  1. Get a sandbox key. Request one in the merchant dashboard. Scoped per environment.
  2. Authenticate. Send the key as a bearer token on every request.
  3. Create a payment. POST to /v1/payments with an amount, currency, and idempotency key.
  4. Subscribe to events. POST your endpoint to /v1/webhooks and verify each signature.
  5. Promote to live. Swap the host and key. The request shape does not change.

Auth

#Authentication

Every request is authenticated with a bearer API key, scoped per environment and per role (read-only, write, admin). Rotate through the dashboard if a key leaks.

Heads-up. The browser tokeniser topropay.js uses a publishable key (pk_). Publishable keys can only create tokens — never read or modify payments.

Environments

#Environments & versioning

EnvironmentBase URLKey prefix
Sandboxhttps://api.sandbox.topropay.comsk_sandbox_*
Livehttps://api.topropay.comsk_live_*

The API version sits in the URL (/v1/). Inside a version we only add — never rename or remove a published field. Breaking changes ship as a new major with a 12-month sunset on the previous one.

Sandbox

Test mode

#Sandbox & test cards

Every sandbox key talks to a full orchestration replica with simulated acquirers, deterministic latencies, and the same webhook signatures as live. The PANs below trigger specific outcomes — use them to test approval, decline, cascade, and 3DS2 paths end-to-end.

PANBrandBehaviour
4242 4242 4242 4242 Visa Frictionless authorisation, captures cleanly.
4000 0000 0000 0002 Visa Hard decline — do_not_honor. Cascade is suppressed.
4000 0000 0000 0119 Visa Soft decline on the first connector, approved on cascade.
5555 5555 5555 4444 Mastercard Frictionless authorisation. Use for two-step capture flows.
4000 0027 6000 3184 Visa Triggers a 3DS2 challenge. Returns requires_action.
4000 0000 0000 9995 Visa Insufficient funds. Useful for refund / partial-capture tests.

Any future expiry (e.g. 12/30) and any 3-digit CVC are accepted. Add ?simulate=connector_timeout to a sandbox request to force an upstream timeout and observe the cascade in flight.

Conventions

#Requests & responses

JSON in, JSON out. Money lives in the smallest currency unit. Timestamps are RFC 3339 in UTC. Unknown fields are rejected — typos never silently succeed.

Resource

Stable

#Payments

A payment is the orchestration object for one cardholder charge across one or more routed attempts. Its status walks pending → authorized → captured (or failed) — the single source of truth your backend should key on.

  • POST /v1/payments Create a payment. Routing runs inside the same call.
  • GET /v1/payments/{id} Retrieve a payment with its full attempt history.
  • POST /v1/payments/{id}/captures Capture an authorised payment, in part or in full.
  • POST /v1/payments/{id}/void Void an authorisation before it is captured.
  • GET /v1/payments List payments. Filter by status, currency, customer, or window.

Create a payment

Send an amount, a currency, and a payment method (token or raw, depending on PCI scope). Add routing: "cascade" to opt into soft-decline recovery in the same call.

State machine

#Payment lifecycle

Six states cover every payment in v1. Key on status; the routing detail under routing.attempts is informational. Terminal states never change again — your back office can mark the order final on receipt.

  1. pending transient

    Accepted by the API. Routing in progress.

  2. requires_action transient

    3DS2 challenge issued. Awaiting cardholder.

  3. authorized transient

    An acquirer authorised. Funds reserved on the card.

  4. captured terminal

    Authorisation captured. Settlement underway.

  5. failed terminal

    Cascade finished without an approval.

  6. voided terminal

    Authorisation cancelled before capture.

Allowed transitions: pending → requires_action, pending → authorized, pending → failed, requires_action → authorized | failed, authorized → captured | voided | failed. Anything else is rejected with 409 invalid_state.

Resource

#Captures

Two-step flow: pass capture: false on a payment to authorise without moving funds, then capture (fully or in part) through the captures endpoint. A capture inherits the connector chosen at authorisation; it is not re-routed.

Resource

Stable

#Refunds

Refunds flow back through the original acquirer. The reconciliation feed ties each refund to its parent payment and the settlement batch — finance closes the loop without a manual match.

  • POST /v1/refunds Refund a captured payment. Defaults to full amount.
  • GET /v1/refunds/{id} Retrieve a refund and its acquirer-side status.
  • GET /v1/refunds List refunds across all payments and connectors.

Resource

Stable

#Customers & tokens

Storing a card creates a network token: a portable, acquirer-agnostic reference that lets the routing engine choose any connector on the next charge.

  • POST /v1/customers Create a customer record and (optionally) tokenise a card.
  • GET /v1/customers/{id} Retrieve a customer and the tokens linked to them.
  • POST /v1/customers/{id}/payment_methods Attach a new payment method to an existing customer.
  • DELETE /v1/customers/{id} Delete a customer. Tokens are revoked at every connector.

Orchestration

#Routing & cascading

The routing field on a payment chooses how the engine behaves: lock to one connector, pick the highest-scoring live route once, or cascade on soft declines before responding.

  • single Pin one connector.
  • best Pick the highest live score.
  • cascade Retry on a soft decline.

Orchestration

#3-D Secure 2

3DS2 runs inside the same authorisation call. Frictionless auth flows return a normal authorized status. When a challenge is required, the payment moves to requires_action with a next_action.redirect_url you hand to the cardholder.

  1. 01POST a payment as usual.
  2. 02Receive requires_action + redirect URL.
  3. 03Cardholder completes the challenge.
  4. 04Final state arrives via payment.authorized or payment.failed.

Events

Stable

#Webhooks

Every state change emits a signed webhook. Subscribe one URL per environment and filter by event type — the catalogue below is exhaustive for v1.

Verifying a delivery

Each delivery carries X-Topropay-Signature: t=<ts>,v1=<hex>. Compute HMAC-SHA256(secret, "<ts>.<raw-body>") and reject when it mismatches, or when the timestamp is older than five minutes.

Delivery & retries

  • at-least-once A delivery may repeat — store and de-duplicate by event.id.
  • retry schedule 8 attempts over 24h with exponential backoff: 30s, 2m, 10m, 30m, 2h, 6h, 12h, 24h.
  • success Any 2xx within 8 seconds counts as acknowledged.
  • replay Re-deliver any event from the dashboard or POST /v1/webhooks/{id}/replay.

Event catalogue

  • payment.created A payment was accepted by the API and entered routing.
  • payment.authorized An acquirer authorised the payment. Funds reserved on the card.
  • payment.captured The authorisation was captured. Funds are moving to settlement.
  • payment.failed All routed attempts declined. The cascade has finished.
  • payment.routed Routing picked a connector. Includes the score and reason.
  • payment.cascaded A soft decline triggered a cascade to a backup connector.
  • refund.created A refund was opened against a captured payment.
  • refund.completed The refund settled at the original acquirer.
  • three_ds.challenged A 3DS2 challenge was issued to the cardholder.
  • settlement.posted A daily settlement batch is available in the reports feed.

Reference

#Errors

Conventional HTTP status codes paired with a stable JSON error object. The type field is the machine-readable identifier — pattern-match on that, never on the message.

StatusTypeMeaning
400 invalid_request The request body is missing a required field or is malformed.
401 unauthorized API key missing, revoked, or not allowed for this environment.
402 payment_declined The acquirer cascade finished without an approval.
403 forbidden The key is valid but lacks the scope for this operation.
404 not_found The resource does not exist, or is in another environment.
409 idempotency_conflict The Idempotency-Key was re-used with a different body.
422 routing_unavailable No live route matches the request constraints.
429 rate_limited You exceeded the rate limit for this key. See Retry-After.
500 internal_error Something on our side failed. Safe to retry with the same key.
503 connector_unavailable Every candidate connector is currently degraded.

Limits

#Rate limits

Default ceiling is 200 requests per second per live key, with bursts to 400. Sandbox is half that. Current state is echoed on every response.

200req/s steady
400burst peak
429respect Retry-After

Lists

#Pagination

Cursor pagination. Pass limit (1–100, default 25) and follow next_cursor on each page until it is null. Cursors are opaque — do not parse them.

Safe writes

#Idempotency

Every write accepts an Idempotency-Key header. Re-sending with the same key returns the original response — never a second payment. Keys live for 24 hours, scoped per API key.

Re-use carefully. A key re-used with a different body responds 409 idempotency_conflict rather than silently mutating state. Generate a fresh UUID per logical operation, not per HTTP retry.

Tools

#SDKs & tooling

  • Server

    Node · Python · Go · PHP · Java

    Thin, typed wrappers around the REST API. Auto-retry on idempotent failures, identical method names per language.

  • Browser

    topropay.js

    Drop-in card collector that tokenises in the browser, keeping your servers out of PCI scope.

  • CLI

    topropay CLI

    Tail webhook deliveries to localhost, replay events, inspect any payment from the terminal.

  • OpenAPI

    Machine spec

    An OpenAPI 3.1 document of every endpoint, schema, and event — point your code generator at it directly.

Reference

#Glossary

The vocabulary the API uses, defined the way our routing engineers use it. Short, opinionated, and consistent with the field names in every payload.

Acquirer
The bank that holds your merchant account and submits transactions to the card networks.
Cascade
Automatically retrying a soft-declined authorisation on a backup connector inside the same payment.
Connector
Any routed endpoint — an acquirer, a PSP, or a local payment method — that topropay can dispatch to.
Idempotency-Key
A client-generated UUID that makes a write safe to retry. Same key + same body → same response.
Network token
A portable, acquirer-agnostic reference to a card. Issued by the scheme, stored by topropay.
Routing score
A live, per-connector approval-probability estimate. Updated continuously from production traffic.
Soft decline
A reversible decline (e.g. issuer timeout, 3DS step-up) — a cascade can recover the payment.
Hard decline
A terminal decline (e.g. lost card, fraud). Cascading is suppressed.

Updates

#Changelog

  1. Added payment.cascaded webhook with per-leg latency.
  2. Network tokenisation enabled by default on all customer creates.
  3. Routing constraints accept card_brand and country.
  4. v1 stabilised. Sunset policy: 12 months for breaking changes.

FAQ

Common questions

Is the API REST or GraphQL?

REST over HTTPS. Predictable resource URLs, standard HTTP verbs, JSON bodies, and HTTP status codes that mean what they say.

Do we need to migrate our acquirers?

No. topropay sits above your existing acquirer and PSP contracts. The API references them as connectors; you keep the commercials.

Where is card data handled?

Inside our PCI DSS Level 2 boundary. Your servers can stay out of scope by tokenising at the browser via topropay.js.

How are webhooks signed?

Each delivery carries an X-Topropay-Signature header — an HMAC-SHA256 of the raw body, keyed with the webhook secret you create.

Is the API stable?

Yes. We version on the URL (currently v1) and never break a published field inside a version. Additions are additive.

Ready to integrate

Write to one API. Reach every acquirer.

Book a 30-minute scoping call. We hand over sandbox keys and stay on Slack while you ship the first integration.