On this page
PayZero API reference
PayZero accepts payments from customers in Japan and reports the outcome back to you. This reference covers the checkout API: the endpoints that list what a customer can pay, open a payment, and confirm whether it settled.
It is arranged to be read in order the first time and searched afterwards. New here? Start with Getting started, then Essentials for how the two payment methods behave, and Guides for testing and going live. Already integrating? The Reference has every endpoint, field by field, with the consolidated error table.
Getting started
Base URL: https://api.payzero.ai
Every endpoint returns JSON. Request bodies are JSON and must be sent with Content-Type: application/json. Responses are never cached.
Requests must come from an allowlisted origin. Send PayZero the origins you will call from before you start; a browser request from any other origin is refused with 403 origin_not_allowed. Server-to-server calls, which send no Origin header, are not subject to the check.
The sandbox
PayZero provisions a sandbox before go-live. It is the same API — the same endpoints, request bodies and response shapes — pointed at the payment provider's test environment, and nothing in it moves real money. Build and test your integration against it, then point the same code at live.
Ask PayZero to set up your sandbox and allowlist your origins to begin. When you are ready to exercise real payments, Testing has the test cards and the authentication PINs, and Sandbox and live covers the one difference between the two environments.
Your first payment
Both methods follow the same shape: create a payment through the API, send the customer to the redirectUrl you get back, and confirm the outcome afterwards. End to end, that is five steps.
- Ask PayZero to allowlist your origins and set up your sandbox.
- For bank transfer, call
GET /checkout/banks, thenGET /checkout/amountsfor the bank the customer picked. For card, go straight to step 3. - Create the payment with
POST /checkout/depositorPOST /checkout/card, and store thereferenceit returns against your order. - Send the customer to
redirectUrl. - Confirm the outcome with
GET /checkout/status?ref=…. The customer's return does not confirm anything by itself.
The smallest single call is GET /checkout/banks: no body, no parameters, just your allowlisted Origin, and it returns live bank availability. Everything else in the flow hangs off the reference you store at step 3.
Essentials
Two payment methods, both redirect flows. This section is how they behave: what the customer does, what the amounts and currencies look like, and why a customer returning from the payment page is not yet a settled payment. The exact request and response shapes are in the Reference.
Authentication
Your secret key authenticates API requests. Send it as a bearer token:
curl -X POST https://api.payzero.ai/checkout/deposit \
-H 'Authorization: Bearer pz_sk_test_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{ ...payment fields... }'The key identifies your merchant and which environment the request runs in: use your test key against the sandbox and your live key in live, so the same request routed with a test key never touches real money. A key looks like pz_sk_test_YOUR_KEY — a prefix that names the environment, then the credential.
Each key is issued from the portal and shown once. Treat it like a password: keep it on your server, never ship it to a browser or commit it to source, and have it reissued from the portal if it leaks. It carries the Authorization header on requests that create or read your own payments; some checkout lookups are public and need no key, so reach for it on server-to-server calls rather than trying to enumerate which endpoints demand it.
Payment methods
Two methods are available. Both are redirect flows: you create the payment through the API, send the customer to the redirectUrl it returns, and confirm the outcome afterwards with GET /checkout/status.
| Bank transfer | Card | |
|---|---|---|
| Currency | JPY | EUR |
| Customer location | Japan | Not restricted |
| Who sets the amount | The customer, from the amounts currently available | You |
| Minimum | The smallest available amount | 1.00 EUR |
| Maximum | The largest available amount | 10,000.00 EUR |
| Payment details entered | On the secure payment page | On the secure payment page |
paymentMethod on settlement | COMMUNITY_BANK | CREDIT_CARD |
Amounts use the currency's normal decimal form, as documented per endpoint. EUR takes two decimal places — "25.00". JPY has none, so a yen amount is a whole number: 500, never 500.00. No sign, no exponent, no thousands separator, no currency symbol.
Bank transfer: the customer selects an available amount
This is the constraint to design around. With bank transfer the customer does not enter an amount. They choose a bank, then choose one of the exact amounts available for that bank at that moment. There is no rounding, no nearest match, no partial fill and no combining of two amounts.
Availability changes continuously. An amount listed a minute ago may be gone, a bank may have nothing available, and the method as a whole can be temporarily unavailable. Design for that:
- Fetch availability immediately before you show it, and refresh it if the customer pauses.
- Build a "no amounts available" state with real copy and a real next action. Nothing has gone wrong when it appears.
- Offer card as a fallback. For a Japanese customer that fallback is a euro charge.
- Bank transfer suits top-ups, wallet funding and account credit. It cannot collect a fixed cart total unless that exact amount happens to be available.
- Reconcile the amount actually paid against your own order afterwards, using the reference.
Cards are charged in euro
Card payments are processed in EUR only. A Japanese cardholder is charged in euro and their own bank sets the exchange rate, so the yen figure on their statement will not be the figure you displayed. Tell them before they pay.
The minimum card payment is 1.00 EUR and the maximum is 10,000.00 EUR. A postal code is required. Card details are entered by the customer on the secure payment page — POST /checkout/card refuses any request containing a card number, expiry, CVV or similar field.
Status and settlement
Settlement is confirmed asynchronously. When the customer finishes on the payment page they are returned to a PayZero page. That return means a browser came back — nothing more.
A customer returning from the payment page does not mean the payment succeeded. Confirm every payment with
GET /checkout/status?ref=…before you release goods or credit an account.
settled | status | Meaning |
|---|---|---|
false | PENDING | No notification yet. The payment may still complete. |
true | APPROVED | The payment completed. Release the goods or credit the account. |
true | DECLINED | The payment did not complete. |
Card payments typically resolve within seconds of the customer finishing on the payment page. Bank transfers take longer, and there is no guaranteed time to settlement. Poll every few seconds while the customer is waiting, then back off to minutes, and treat a pending payment as a normal state in your order flow rather than an error.
Guides
The things worth getting right before you ship: how to test an integration against the sandbox, and what changes when you move to live.
Testing
PayZero provisions a sandbox environment before go-live. It is the same API — the same endpoints, request bodies and response shapes — pointed at the payment provider's test environment. Nothing in it moves real money, and every card number below is a test number that works in sandbox only.
Use a different customer email for each test payment. Repeating the same customer details, bank and amount while an earlier payment is still unfinished can be refused, and it reaches you as psp_error rather than as anything that names the cause.
Test cards
The customer enters these on the secure payment page.
| Brand | Number | Expiry | CVV |
|---|---|---|---|
| Visa | 4012888888881881 | 12/30 | 123 |
| Mastercard | 5500000000000004 | 12/30 | 123 |
| JCB | 3530111333300000 | 12/30 | 123 |
| Visa | 4900000000000086 | 12/30 | 123 |
Which of these you use does not decide the outcome. Nothing about a test number makes a payment approve or decline — the authentication step does, and that is the next section.
Forcing an authentication outcome
A card payment in sandbox will usually present an authentication step after the redirect: a test authentication page with a single PIN field. The PIN decides the result, and the page lists the ones it accepts.
| PIN | Result |
|---|---|
mby | Authentication approved |
mbn | Authentication declined |
mbu | Unknown |
mba | Attempted |
This is how to test what your integration does with a failed authentication, and it needs no special card: open an ordinary card payment with any number above, send the customer to redirectUrl, and submit mbn. Expect the authentication step on sandbox card payments as a matter of course — a payment that never reaches it is the exception, not the rule.
The PIN decides the authentication, not the payment. The outcome still arrives through GET /checkout/status, exactly as in live.
Bank transfer in sandbox
The amounts a sandbox bank offers are live test inventory, not a fixture. Expect the list to differ between runs, and expect it to be empty sometimes — for one bank, or for every bank at once. Sandbox behaves the way live behaves here, which makes it the place to exercise that constraint rather than something to work around; see Bank transfer: the customer selects an available amount. Do not write a test that depends on a particular amount being available.
Creating a deposit reserves that amount immediately, in sandbox as well as live, whether or not anyone completes it. Looping over POST /checkout/deposit will exhaust the amounts available to your next test.
Confirming a test payment
A customer returning from the payment page means a browser came back and nothing more, in sandbox exactly as in live. GET /checkout/status?ref=… is what confirms a payment: poll it until settled is true, then read status. See Status and settlement.
What sandbox cannot simulate
- A specific card decline reason. You can decline the authentication; you cannot choose the reason an issuer would refuse the charge.
- A dispute. There is no way to raise one against a test payment.
- A settlement notification on demand. A payment notifies when it settles, and there is no way to ask for that notification again.
Sandbox and live
Sandbox and live are the same API pointed at different environments, so anything you build against the checkout endpoints — banks, amounts, deposit, card, status — works the same in both, and bank transfer in particular behaves identically.
The one difference is how a card is captured. In live, card details are entered by the customer on the secure payment page: you call POST /checkout/card and send them to the redirectUrl. The Card API (sandbox) — hosted sessions and direct card — accepts card details through PayZero for building and testing, and does not run in live; a card API request against a live environment is answered 403 card_direct_sandbox_only before its body is read.
PayZero provisions your sandbox before go-live. When your integration is working against it, the same code points at live — nothing in the checkout flow changes but the card-capture path above.
Card API (sandbox)
Everything in this section runs in sandbox only. These endpoints accept card details through PayZero — on a PayZero payment page, or directly in the request — instead of on the secure payment page. They exist for building and testing an integration against the sandbox PayZero provisions for you (see Testing). The server refuses to run them in live: a card API request against a live environment is answered 403 card_direct_sandbox_only before the body is read. They are not a way to take a real payment.
The card API is not enabled by default and is not open. Ask PayZero to enable it on your sandbox; a key is issued at the same time. This is a distinct sandbox-only credential — not the API secret key from Authentication: it gates the sandbox direct-card flow and has no meaning in live. Send it in the request body as key — never in a query string, where it would sit in access logs and browser history. The examples below show it as <your-card-direct-key>. A missing or wrong key is answered 403 card_direct_key_invalid, and the response does not say which.
Use the test cards; never send a real card number to these endpoints. Sandbox card payments present the authentication step described in Forcing an authentication outcome, and the outcome arrives through GET /checkout/status, exactly as Status and settlement describes — nothing in this section changes how a payment is confirmed.
Two flows. With a hosted checkout session your server opens a session and sends the customer to a PayZero payment page; they enter their card there and come back to your returnUrl. With direct card your server sends the card fields itself and steers the customer through the authentication step from the response. Creating a session, paying it, and a direct card request all count as payment creation for rate limiting.
Hosted checkout session
Create a session, send the customer to the url it returns, they enter their card on PayZero's page, and they come back to your returnUrl with the result appended. A session expires 15 minutes after it is created and can be paid once.
POST /checkout/session
Opens a checkout session and returns the URL of the payment page. Server to server: this is the call that carries your key, so it is not for a browser.
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key PayZero issued for your sandbox. |
amount | string | number | Yes | Plain decimal, at most two places. Between 1.00 and 10000.00. |
currency | string | No | EUR. Defaults to EUR. |
firstName | string | Yes | Up to 40 characters. |
lastName | string | Yes | Up to 40 characters. |
email | string | Yes | Customer's email address. Up to 120 characters. |
dateOfBirth | string | Yes | YYYY-MM-DD. |
postCode | string | Yes | Up to 12 characters. |
country | string | No | ISO 3166-1 alpha-2. Defaults to JP. |
city | string | No | Up to 40 characters. Defaults to Tokyo. |
address | string | No | Up to 80 characters. |
returnUrl | string | Yes | Absolute URL, up to 512 characters. The customer is returned here after paying. |
returnUrl must be an absolute URL whose origin is on your origin allowlist — the same list browser requests are checked against — or the session is refused with 400 bad_return_url. The path and query are yours; the result is appended to whatever they carry.
curl -X POST https://api.payzero.ai/checkout/session \
-H 'Content-Type: application/json' \
-d '{
"key": "<your-card-direct-key>",
"amount": "25.00",
"currency": "EUR",
"firstName": "Aoi",
"lastName": "Tanaka",
"email": "aoi.tanaka@example.com",
"dateOfBirth": "1990-04-18",
"postCode": "100-0001",
"returnUrl": "https://checkout.example.com/return"
}'{
"sessionId": "0f31c7a89bd44e21a6c05d9e8f3b7a12",
"url": "https://sandbox.payzero.ai/pay/0f31c7a89bd44e21a6c05d9e8f3b7a12",
"expiresAt": "2026-07-22T09:15:00.000Z"
}The response is 201. Send the customer to url before expiresAt, which is ISO 8601 UTC, fifteen minutes after creation. No card is sent at this step and none may be — the card is entered once, on the payment page. The amount and customer are fixed when the session is created; nothing entered on the payment page can change them.
Errors: bad_json, bad_amount, currency_not_supported, bad_name, bad_email, bad_dob, bad_country, bad_post_code, bad_return_url, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, internal.
GET /checkout/session/:id
What the payment page reads to render the checkout. You do not call it yourself. Anyone holding the id can, so it answers with the minimum a card form needs — no customer detail comes back.
{
"amount": "25.00",
"currency": "EUR",
"expiresAt": "2026-07-22T09:15:00.000Z",
"status": "open"
}status is open on every 200; there is no other value. A session that is unknown, expired or already paid is answered 404 session_not_found, with a body that is identical in all three cases.
Errors: session_not_found, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, origin_not_allowed, rate_limited, internal.
POST /checkout/session/:id/pay
The payment page posts the card, once. The body is the card and nothing else — no amount, no customer, no key. Those were fixed when the session was created, and any other field sent here is ignored unread.
| Field | Type | Required | Description |
|---|---|---|---|
cardNumber | string | Yes | 12–19 digits, sent as a string. Spaces and hyphens are ignored. A test card number, never a real one. |
expDate | string | Yes | MM-YY or MM-YYYY, with - or /. Must not be in the past. |
cvv | string | Yes | 3 or 4 digits. |
nationalId | string | Yes | 4–32 letters, digits and hyphens. |
browser | object | No | The customer's browser data for the authentication step: acceptHeader, javaEnabled, language, colorDepth, screenHeight, screenWidth, timeZoneOffset. Missing values fall back to sandbox defaults. |
{
"reference": "PZ-MJ4K6D8R-5B2F9C31",
"status": "PENDING",
"requires3ds": true,
"redirectUrl": "https://secure.example-psp.com/3ds?token=41c9e2"
}reference is PayZero's identifier for the payment, and what GET /checkout/status answers on. status is always PENDING — entering a card settles nothing. The customer is sent to redirectUrl: the authentication step when requires3ds is true, the client's returnUrl directly when it is not. Either way they arrive back at the returnUrl with ?ref= carrying the reference and result=completed or result=failed appended. result says which way the customer came back, not whether money moved — confirm with GET /checkout/status before crediting anything.
Paying spends the session: a second call with the same id is answered 404 session_not_found and opens no second payment.
Errors: bad_json, bad_card_number, bad_expiry, bad_cvv, bad_national_id, session_not_found, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.
Direct card
POST /checkout/card-direct
The server-to-server variant: one request carries the key, the amount, the customer and the card, and opens the payment. There is no session and no PayZero page — your integration collects the card and sends the customer to the authentication step itself.
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key PayZero issued for your sandbox. |
amount | string | number | Yes | Plain decimal, at most two places. Between 1.00 and 10000.00. |
currency | string | No | EUR. Defaults to EUR. |
firstName | string | Yes | Up to 40 characters. |
lastName | string | Yes | Up to 40 characters. |
email | string | Yes | Customer's email address. Up to 120 characters. |
dateOfBirth | string | Yes | YYYY-MM-DD. |
postCode | string | Yes | Up to 12 characters. |
country | string | No | ISO 3166-1 alpha-2. Defaults to JP. |
city | string | No | Up to 40 characters. Defaults to Tokyo. |
address | string | No | Up to 80 characters. |
cardNumber | string | Yes | 12–19 digits, sent as a string. Spaces and hyphens are ignored. A test card number, never a real one. |
expDate | string | Yes | MM-YY or MM-YYYY, with - or /. Must not be in the past. |
cvv | string | Yes | 3 or 4 digits. |
nationalId | string | Yes | 4–32 letters, digits and hyphens. |
browser | object | No | As on POST /checkout/session/:id/pay. |
curl -X POST https://api.payzero.ai/checkout/card-direct \
-H 'Content-Type: application/json' \
-d '{
"key": "<your-card-direct-key>",
"amount": "25.00",
"currency": "EUR",
"firstName": "Aoi",
"lastName": "Tanaka",
"email": "aoi.tanaka@example.com",
"dateOfBirth": "1990-04-18",
"postCode": "100-0001",
"cardNumber": "4012888888881881",
"expDate": "12-30",
"cvv": "123",
"nationalId": "AB123456"
}'{
"reference": "PZ-MJ4K7E3S-8C4A1D62",
"transactionId": "TR-8842164",
"amount": "25.00",
"currency": "EUR",
"status": "PENDING",
"requires3ds": true,
"redirectUrl": "https://secure.example-psp.com/3ds?token=7d20b5"
}status is always PENDING. requires3ds says whether an authentication step stands between the customer and the outcome, and redirectUrl is where it happens: a response carrying one is a payment waiting on a step nobody has completed, so send the customer there and expect the PIN page from Forcing an authentication outcome. With no redirectUrl there is nothing to complete. In both cases the outcome arrives through GET /checkout/status?ref=…, and nothing is settled until it reports so. After the authentication step the customer finishes on a PayZero page — this flow has no returnUrl.
Errors: bad_json, bad_amount, currency_not_supported, bad_name, bad_email, bad_dob, bad_country, bad_post_code, bad_card_number, bad_expiry, bad_cvv, bad_national_id, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.
Reference
Every endpoint, field by field, with a runnable example and the errors it can return, then the consolidated error table, what the API does not do, and how to reach support. The flow these fit into is in Getting started.
GET /checkout/banks
Lists the banks available for bank transfer, each with a snapshot of what it currently has on offer. Takes no parameters.
curl https://api.payzero.ai/checkout/banks \
-H 'Origin: https://checkout.example.com'{
"banks": [
{ "swiftCode": "MHCBJPJT", "bankName": "Mizuho Bank", "available": 3, "amounts": [100, 500, 777] },
{ "swiftCode": "BOTKJPJT", "bankName": "MUFG Bank", "available": 0, "amounts": [] }
]
}swiftCode is the bank identifier you pass to the other bank transfer endpoints. available is how many amounts the bank has on offer right now, and amounts lists the distinct ones, ascending. Banks are ordered by available, highest first; available: 0 means that bank has nothing on offer at this moment and is not an error. Use this endpoint to build a bank picker, not to reserve anything — availability is confirmed again when you create the payment.
Errors: origin_not_allowed, rate_limited, psp_error, internal.
GET /checkout/amounts
The exact amounts currently available for one bank.
| Parameter | Type | Required | Description |
|---|---|---|---|
bank | string | Yes | SWIFT code, 8–11 letters and digits. Case-insensitive. |
curl 'https://api.payzero.ai/checkout/amounts?bank=MHCBJPJT' \
-H 'Origin: https://checkout.example.com'{
"swiftCode": "MHCBJPJT",
"amounts": [
{ "id": 84213, "amount": 100, "currency": "JPY" },
{ "id": 84219, "amount": 777, "currency": "JPY" }
]
}Each entry is one amount the customer can choose: id is what you send as activeAmountId, amount is a whole number of yen, and currency is JPY.
An empty amounts array means the bank has nothing available. It is a normal answer, not an error. Identifiers rotate within minutes, so fetch this immediately before the customer chooses.
Errors: bad_bank, origin_not_allowed, rate_limited, psp_error, internal.
POST /checkout/deposit
Opens a bank transfer payment for one of the available amounts and returns the URL to send the customer to.
| Field | Type | Required | Description |
|---|---|---|---|
swiftCode | string | Yes | SWIFT code from GET /checkout/banks. |
activeAmountId | number | Yes | The id of the amount the customer chose. |
amount | number | Recommended | The amount the customer chose. See the note below. |
firstName | string | Yes | Up to 40 characters. |
lastName | string | Yes | Up to 40 characters. |
email | string | Yes | Customer's email address. Up to 120 characters. |
dateOfBirth | string | Yes | YYYY-MM-DD. |
city | string | No | Up to 40 characters. Defaults to Tokyo. |
address | string | No | Up to 80 characters. |
Send amount as well as activeAmountId. Identifiers rotate, and if the one you hold has changed since you fetched it, PayZero binds another entry at the same amount instead of failing. Without amount a rotated identifier returns 409 slot_taken. The authoritative amount is always the one PayZero confirms, never the one in your request.
curl -X POST https://api.payzero.ai/checkout/deposit \
-H 'Content-Type: application/json' \
-H 'Origin: https://checkout.example.com' \
-d '{
"swiftCode": "MHCBJPJT",
"activeAmountId": 84219,
"amount": 777,
"firstName": "Aoi",
"lastName": "Tanaka",
"email": "aoi.tanaka@example.com",
"dateOfBirth": "1990-04-18",
"city": "Tokyo",
"address": "1-1 Chiyoda"
}'{
"reference": "PZ-MJ4K2X9A-7F3C1B92",
"transactionId": "TR-8842119",
"amount": 777,
"currency": "JPY",
"status": "WAITING",
"redirectUrl": "https://secure.example-psp.com/s/9f2ac1"
}reference is PayZero's identifier for this payment — store it against your order. transactionId is the payment provider's. amount is the confirmed amount in JPY. status describes the redirect step and is not a payment outcome. Send the customer to redirectUrl to authorise the transfer.
409 slot_taken means the amount was taken between your two calls. The response body carries a fresh amounts array in the same shape as GET /checkout/amounts, so you can re-offer the choice without another round trip.
Errors: bad_json, bad_bank, bad_amount, bad_name, bad_email, bad_dob, slot_taken, invalid, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.
POST /checkout/card
Opens a card payment and returns the URL of the secure payment page.
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | number | Yes | Plain decimal, at most two places. Between 1.00 and 10000.00. |
currency | string | No | EUR. Defaults to EUR. |
firstName | string | Yes | Up to 40 characters. |
lastName | string | Yes | Up to 40 characters. |
email | string | Yes | Customer's email address. Up to 120 characters. |
dateOfBirth | string | Yes | YYYY-MM-DD. |
postCode | string | Yes | Up to 12 characters. |
country | string | No | ISO 3166-1 alpha-2. Defaults to JP. |
city | string | No | Up to 40 characters. Defaults to Tokyo. |
address | string | No | Up to 80 characters. |
Do not send card details. A request containing a card number, expiry, CVV, PIN or track data — under any spelling, at any nesting depth — is refused whole with 400 card_data_not_accepted, and nothing in it is read or forwarded. In sandbox there is a separate card API that does accept card details, for building and testing only — see Card API (sandbox).
curl -X POST https://api.payzero.ai/checkout/card \
-H 'Content-Type: application/json' \
-H 'Origin: https://checkout.example.com' \
-d '{
"amount": "25.00",
"currency": "EUR",
"firstName": "Aoi",
"lastName": "Tanaka",
"email": "aoi.tanaka@example.com",
"dateOfBirth": "1990-04-18",
"postCode": "100-0001",
"country": "JP",
"city": "Tokyo",
"address": "1-1 Chiyoda"
}'{
"reference": "PZ-MJ4K3B1Y-2D8E4A07",
"transactionId": "TR-8842137",
"amount": "25.00",
"currency": "EUR",
"status": "PENDING",
"redirectUrl": "https://secure.example-psp.com/pay?token=8f21c0"
}reference is PayZero's identifier for this payment — store it against your order. amount comes back normalised to two decimal places. status is always PENDING here: no card has been entered yet. Send the customer to redirectUrl to enter their card.
Errors: bad_json, card_data_not_accepted, currency_not_supported, bad_amount, bad_name, bad_email, bad_dob, bad_country, bad_post_code, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.
GET /checkout/status
The current state of one payment. Works for both methods.
| Parameter | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | The reference returned when the payment was created. |
curl 'https://api.payzero.ai/checkout/status?ref=PZ-MJ4K3B1Y-2D8E4A07' \
-H 'Origin: https://checkout.example.com'{
"reference": "PZ-MJ4K3B1Y-2D8E4A07",
"settled": true,
"status": "APPROVED",
"amount": "25.00",
"currency": "EUR",
"paymentMethod": "CREDIT_CARD",
"source": "ipn"
}| Field | Type | Description |
|---|---|---|
reference | string | The reference you asked about. |
settled | boolean | true once PayZero has received and authenticated a settlement notification. |
status | string | PENDING until a notification arrives, then APPROVED or DECLINED. |
amount | string | The amount. Present once the payment is known. |
currency | string | ISO 4217 code. Present once the payment is known. |
paymentMethod | string | COMMUNITY_BANK or CREDIT_CARD. Present once a notification has arrived. |
source | string | ipn, deposit or inbox. See below. |
source says where the answer came from: ipn — an authenticated settlement notification. deposit — the payment exists and no notification has arrived yet. inbox — no record was found for this reference.
An unrecognised reference is not an error. A typo returns settled: false, status: "PENDING" and source: "inbox", which is indistinguishable from a payment that has not settled yet, so check the reference you stored rather than inferring existence from this endpoint.
This endpoint is not authenticated: anyone holding a reference can read its amount, currency and status. Treat a reference as you would an order number that should not be published.
Errors: bad_reference, origin_not_allowed, rate_limited, internal.
Errors
Every error response carries an error code string, and most also carry a human-readable message — { "error": "bad_amount", "message": "…" }. Some carry extra fields, noted below. Match on error, never on message.
error | HTTP | When it occurs | What to do |
|---|---|---|---|
origin_not_allowed | 403 | The Origin header is not allowlisted. | Ask PayZero to allowlist it, or call server to server. |
rate_limited | 429 | Too many requests in a short period. Creating payments is limited more tightly than reading. | Back off and retry. Never retry a create blindly — each accepted call opens a new payment. |
bad_json | 400 | The body is not valid JSON. | Fix the request. |
bad_bank | 400 | bank or swiftCode is missing or is not 8–11 letters and digits. | Use a swiftCode from GET /checkout/banks. |
bad_amount | 400 | On deposit, activeAmountId is missing or not positive. On card, the amount is not a plain decimal, has too many decimal places, or is outside 1.00–10,000.00 EUR. | Read message; it names the rule that failed. |
bad_name | 400 | firstName or lastName is empty. | Collect both. |
bad_email | 400 | email is missing or malformed. | Validate before sending. |
bad_dob | 400 | dateOfBirth is not YYYY-MM-DD. | Reformat. |
bad_country | 400 | country is not two letters. | Send an ISO 3166-1 alpha-2 code. |
bad_post_code | 400 | postCode is missing on a card payment. | Collect a postal code. |
card_data_not_accepted | 400 | The body contains cardholder data. field names which one. | Remove it. Card details are entered on the payment page. |
currency_not_supported | 400 | currency is not offered for cards. supported lists what is. | Send EUR. |
slot_taken | 409 | The chosen amount was taken before the payment could be opened. amounts carries current availability. | Show the fresh amounts and let the customer choose again. |
invalid | 400 | The amount stopped being available between your request and the reservation. | Refresh availability and let the customer choose again. |
rejected | 400 | The payment provider refused the request because of its contents. code and message are theirs. | Correct the field named in message. |
bad_reference | 400 | ref is missing on GET /checkout/status. | Send the stored reference. |
bad_return_url | 400 | Card API: returnUrl is missing, not an absolute URL, or its origin is not allowlisted. | Send a URL on an allowlisted origin. |
bad_card_number | 400 | Card API: the card number is not 12–19 digits, or fails its check digit. | Fix the number. Test cards only. |
bad_expiry | 400 | Card API: the expiry is not MM-YY or MM-YYYY, or is in the past. | Reformat, or use an unexpired date. |
bad_cvv | 400 | Card API: the security code is not 3 or 4 digits. | Fix it. |
bad_national_id | 400 | Card API: nationalId is missing or not 4–32 letters, digits and hyphens. | Collect one. |
session_not_found | 404 | Card API: the checkout session is unknown, expired or already paid. The three cases are answered identically. | Create a new session. |
card_direct_sandbox_only | 403 | A card API request reached a live environment. The body is not read and nothing is called. | Use your sandbox. The card API does not run in live. |
card_direct_disabled | 403 | The card API is not enabled on this deployment. | Ask PayZero to enable it on your sandbox. |
card_direct_key_invalid | 403 | The key is missing or wrong. The response does not say which. | Send the key PayZero issued, in the request body. |
session_storage_unavailable | 503 | This deployment cannot store checkout sessions. | Contact PayZero. |
merchant_suspended | 403 | The account cannot currently accept payments. | Contact PayZero. |
unknown_merchant | 400 | The account could not be resolved. | Contact PayZero. |
not_found | 404 | No such endpoint. | Check the path. |
psp_error | 502 | The payment provider returned an error. message carries theirs. | Retry after a short pause. If it persists, contact PayZero with the time and reference. |
internal | 500 | Unexpected error. | Retry. If it persists, contact PayZero. |
Not currently available
| Not available | Notes |
|---|---|
| Any authentication scheme but the bearer secret | Requests carry your secret key as a bearer token — see Authentication. Browser requests are additionally checked against the origin allowlist, and the card API adds its own separate sandbox link key. |
| Outbound webhooks | Poll GET /checkout/status for each reference. |
| Refunds | Not available through the API. |
| Payouts | Not available through the API. |
| Recurring billing, subscriptions and card-on-file | Not available. |
| Card payments in JPY | Cards are EUR only. |
| Card capture in live | Taking card details through PayZero — the card API — runs against the sandbox only, for building and testing. In live, card details are entered on the secure payment page. |
| A return URL to your own site | The customer finishes on a PayZero page. In sandbox, a hosted checkout session returns the customer to your returnUrl. |
| Order ids, customer ids or other metadata | The create endpoints accept no such field. Store the reference against your order. |
| Idempotency keys | Each accepted POST opens a separate payment. |
Support
Lead with the reference. It looks like PZ-MJ4K2X9A-7F3C1B92, it is returned when the payment is created, and it identifies that payment everywhere — in the API, at the payment provider, and on the settlement notification. With it a payment can be traced end to end. Without it, very little can be answered.
Email info@payzero.ai and include:
| Include | Why |
|---|---|
The reference (PZ-…) | The identifier every record is keyed on. |
The transactionId, if you have it | The payment provider's side of the same event. |
| Environment — sandbox or live | They are separate systems. |
| Method — bank transfer or card | The two behave differently. |
| Amount and currency as displayed to the customer | The figure to reconcile against. |
| When it happened, with a time zone | For log correlation. |
| What the customer saw, verbatim, including any error text | The message usually names the field at fault. |
| Whether they completed the payment page | The most useful single fact for a bank transfer. |
Do not send card numbers. The API refuses them and so do we.