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:
Frictionless. HTTP 201,
status: "succeeded", identical to a normal sale.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 tosuccess_urlorfailure_urlwith?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_urlandfailure_urlare required on every card payment whenever the merchant's connection has 3-D Secure enabled — including the frictionless happy path and payments that never setauthentication: "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_nameis 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
- Idempotency — pick the right
Idempotency-Key. - Errors — every error code we return.
- Test cards — test-mode card numbers.
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article