Payments

Created by Kalin Ivanov, Modified on Tue, 23 Jun at 8:41 AM by Kalin Ivanov

Create, retrieve, and list payments.

POST /api/v1/payments is synchronous and auto-captured — when the response comes back, the funds are already on their way (or the card has been refused). Idempotency-Key is required.

The Payment object

{
  "id": "pay_01HABCXYZ...",
  "status": "succeeded",
  "amount": 2999,
  "currency": "CHF",
  "reference": "order_1001",
  "customer": "cus_01HCUSXYZ...",
  "payment_method": {
    "type": "card",
    "brand": "visa",
    "last4": "1111",
    "exp_month": 3,
    "exp_year": 2030
  },
  "next_action": null,
  "failure": null,
  "created_at": "2026-05-20T12:00:00Z"
}
Field Type Notes
id string Stable, prefixed with pay_.
status string succeeded · failed · requires_action (3DS challenge pending).
amount integer Minor units (cents/rappen).
currency string ISO 4217 (CHF, EUR, USD, etc.).
reference string Your own reference, free text.
customer string | null cus_... ID of the attached customer, if any.
payment_method object Brand, last 4, expiry — never full PAN.
next_action object | null Present only when status: "requires_action".
failure object | null Present only when status: "failed".
created_at string ISO 8601 UTC.

Create a payment

POST /api/v1/payments

Required headers

Authorization: Bearer sk_test_...
Content-Type: application/json
Idempotency-Key: <unique-per-attempt>

Required body fields

Field Type Notes
amount integer Minor units. Must be > 0.
currency string ISO 4217.
payment_method object See below.

payment_method (card)

Field Type Notes
type string card
number string The card number. In test mode use a test card; in live mode, the shopper’s real card number.
exp_month integer 1-12.
exp_year integer 4-digit year.
cvc string 3 or 4 digits.
holder_name string Required. The name on the card.

Optional body fields

Field Type Notes
reference string Your own reference. Free text.
customer string | object Either an existing cus_... ID or an inline customer payload.
metadata object Up to 20 string keys, values ≤ 500 chars. Echoed back on read.
shopper_ip string The shopper's IP. Improves fraud scoring.
browser_info object Browser signals -- see Browser info & 3-D Secure. For 3DS challenges, the full set is required; the two header fields (user_agent, accept_header) are enough for non-3DS fraud scoring.
browser_info_token string Compact opaque token produced by Swisspay.collectBrowserInfo() (see Browser info & 3-D Secure). Lets you ship 3DS browser signals from the shopper's browser to your server in one short string, instead of passing every browser_info field by hand. Precedence: inline browser_info > browser_info_token > request headers.
risk_data object Nested risk signals (basket value, tenure, promo). Forwarded to the upstream risk engine.
authentication string automatic to opt into 3-D Secure.
origin string Scheme + host (+ optional port) of the checkout page in the shopper's browser, e.g. https://shop.example.com. Required only when you render checkout / 3DS on your own domain and want the 3DS2 origin to match the browser's page origin. Must be https in production; no path, query, fragment, or userinfo; <= 80 chars. Omit when redirecting to SwissPay-hosted checkout.
success_url / failure_url string Required on every card payment when the connection has 3-D Secure enabled (the default for Adyen connections, in both test and live mode) -- not only when the request sets authentication: "automatic". Both must be HTTPS with no fragment.

Example — successful Visa

curl -X POST https://app.swisspay.ai/api/v1/payments \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount": 2999,
    "currency": "CHF",
    "reference": "order_1001",
    "payment_method": {
      "type": "card",
      "number": "4111111111111111",
      "exp_month": 3,
      "exp_year": 2030,
      "cvc": "737",
      "holder_name": "Jane Doe"
    }
  }'

Example — with an inline customer

The customer is created if it doesn't already exist (matched by email within your account) and attached to the payment.

{
  "amount": 2999,
  "currency": "CHF",
  "reference": "order_1003",
  "customer": {
    "email": "new@example.com",
    "name": "New Customer",
    "external_id": "user_99"
  },
  "payment_method": { /* card */ }
}

Example — with fraud signals

{
  "amount": 2999,
  "currency": "CHF",
  "reference": "order_risk_1004",
  "shopper_ip": "203.0.113.42",
  "browser_info": {
    "user_agent": "Mozilla/5.0 (Macintosh) AppleWebKit/605",
    "accept_header": "text/html,application/xhtml+xml"
  },
  "risk_data": {
    "basket": { "value": 2999, "items_count": 2 },
    "customer_tenure_days": 184,
    "promo_code": "WELCOME"
  },
  "payment_method": { /* card */ }
}

These fields are forwarded to our upstream risk engine but not echoed back on the response. Confirm acceptance by checking HTTP 201 and status: "succeeded".

Example — 3-D Secure (automatic)

{
  "amount": 8999,
  "currency": "EUR",
  "reference": "order_3ds_visa",
  "authentication": "automatic",
  "success_url": "https://merchant.example/checkout/return",
  "failure_url": "https://merchant.example/checkout/return",
  "payment_method": {
    "type": "card",
    "number": "4871049999999910",
    "exp_month": 3,
    "exp_year": 2030,
    "cvc": "737",
    "holder_name": "Jane Doe"
  }
}

Two possible outcomes:

  1. Frictionless. HTTP 201, status: "succeeded", identical to a normal sale.

  2. Challenge required. HTTP 200, status: "requires_action", plus:

    "next_action": {
      "type": "redirect_to_url",
      "redirect_url": "https://app.swisspay.ai/checkout/3ds/3ds_..."
    }
    

    Redirect the customer to next_action.redirect_url. After they complete (or abandon) the challenge, we redirect them to success_url or failure_url with ?payment_id=pay_... appended.

The source of truth for the final state is GET /api/v1/payments/:id.

Collecting 3DS browser info

A 3-D Secure browser challenge needs more from the shopper's browser than the request headers alone can supply (screen size, language, color depth, time zone, etc.). The easiest way to collect those signals -- without writing the snippet yourself -- is to embed our hosted helper on your checkout page and pass the resulting browser_info_token to your server. See Browser info & 3-D Secure for the full integration.

Hosting checkout on your own domain

If you render the card form (and the 3DS challenge) on your own domain rather than redirecting to SwissPay-hosted checkout, pass that page's origin in origin so the 3DS2 request matches the shopper's browser origin:

{
  "amount": 8999,
  "currency": "EUR",
  "authentication": "automatic",
  "origin": "https://shop.example.com",
  "success_url": "https://shop.example.com/checkout/return",
  "failure_url": "https://shop.example.com/checkout/return",
  "payment_method": { "type": "card", "...": "..." }
}

Omit origin when you redirect shoppers to SwissPay-hosted checkout -- we will use our own origin.

Validation rules for return URLs

  • success_url and failure_url are required on every card payment whenever the merchant's connection has 3-D Secure enabled — including the frictionless happy path and payments that never set authentication: "automatic". Adyen connections ship with 3DS enabled in both test and live mode, so treat these URLs as required unless you have explicitly disabled 3DS on the connection.
  • Both must be HTTPS. No URL fragment (#...) allowed.
  • payment_method.holder_name is required on every card payment.

Configuring 3DS

3DS is enabled per processor connection from Settings → Connected providers in the dashboard.


Retrieve a payment

GET /api/v1/payments/{id}

Returns the same Payment object as POST /payments.

curl https://app.swisspay.ai/api/v1/payments/pay_01HABCXYZ \
  -H "Authorization: Bearer sk_test_..."

Unknown IDs — including IDs that belong to another merchant — return 404 Not Found with no body.


List payments

GET /api/v1/payments?page=1&per_page=20
Query param Default Max
page 1
per_page 20 100
customer A cus_... ID. Returns only payments attached to that customer. Unknown IDs return an empty list (no cross-account leak).

Example — payments for one customer

curl 'https://app.swisspay.ai/api/v1/payments?customer=cus_01HCUSXYZ' \
  -H "Authorization: Bearer sk_test_..."

Response:

{
  "data": [ /* payment objects */ ],
  "page": 1,
  "per_page": 20,
  "total_count": 156,
  "has_more": true
}

See also

Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article