Checkout
The browser half of a payment session: what a checkout page calls.
| Endpoint | What it does |
|---|---|
GET /v1/checkout/sessions/{id} |
Retrieve a checkout session |
POST /v1/checkout/sessions/{id}/confirm |
Confirm a checkout session |
POST /v1/checkout/sessions/{id}/otp |
Submit a customer’s one-time code |
POST /v1/checkout/sessions/{id}/otp/resend |
Send the customer a new one-time code |
Try any of them in the API tester.
Retrieve a checkout session
Section titled “Retrieve a checkout session”GET /v1/checkout/sessions/{id}
What a checkout page needs to render: the amount, the currency, the payee’s display name, the description and the channels this session accepts.
Requires the publishable key in Authorization AND the session’s client token in X-Paysigna-Client-Token. A wrong token and a wrong id are the same 404, so this endpoint cannot be used to discover which sessions exist.
This is also the POLLING endpoint. After confirming, call it until payment.status is terminal. The payment block is read live from the charge on every call, so it can never be stale.
It returns NOTHING about the merchant’s account, balances, fees, our partners or our risk controls, and never a full customer identifier.
Authentication: a publishable key (safe in a browser) with the scope payment_session.read.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
X-Paysigna-Client-Token |
string | yes | The client token your server received when it created the session. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The payment session’s id, which your server hands to the checkout page with the client token. |
Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
amount |
integer | In minor units. Read-only: this endpoint cannot change it and neither can confirming. |
channels |
array | The channels this session accepts. Confirming with anything else is refused. |
currency |
string | ISO 4217 code of amount. |
description |
string | What the payment is for, to show the customer. |
display_name |
string | Who the customer is paying. |
expires_at |
string (date-time) | After this the session cannot be paid. Create a new one. |
id |
string | The session’s id. |
object |
string | |
payment |
object | |
payment.action |
object | |
payment.action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
payment.action.expires_at |
string (date-time) | When the partner stops accepting this action, where it says. For otp, a code sent after this is refused; send a new one first if resendable. |
payment.action.message |
string | What to tell the customer, when the type alone does not say: for otp, where the code is (e-mailed to them, or shown in their bank’s app). Safe to show as is. |
payment.action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
payment.action.type |
string | prompt: the customer approves a prompt on their phone. otp: they enter a one-time code, which you send to POST /v1/charges/{id}/otp. 3ds_redirect or hosted_checkout: send them to url. ussd: they dial a code (reserved, not currently used). One of: prompt, otp, 3ds_redirect, hosted_checkout, ussd |
payment.action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
payment.amount |
integer | What the customer is actually debited, including any fee they bear. |
payment.completed_at |
string (date-time) | When the payment reached a final state. |
payment.customer_masked |
string | A partial identifier. The full value is never returned. |
payment.failure_message |
string | Safe to show the customer. PaySigna’s internal failure classification is not exposed. |
payment.reference |
string | Quote this to the merchant’s support, not to PaySigna. |
payment.status |
string | The payment’s state. Keep polling while it is initiated, pending, processing or awaiting_action; succeeded means paid; failed, cancelled or expired means nothing was collected. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired, reversed, refunded, partially_refunded |
return_url |
string | Where to send the customer when the flow ends. |
status |
string | created: waiting for the customer. confirming: a confirmation is in progress. completed: a payment was started; read payment for its result. cancelled or expired: the session can no longer be paid. One of: created, confirming, completed, cancelled, expired |
Errors
Section titled “Errors”400: the request is malformed401: the key is missing, wrong or revoked403: the key lacks the scope, or your account may not do this404: no such resource on this account422: a field is invalid;errorsnames it429: too many requests; wait forRetry-After500: Internal Server Error503: the payment partner is unreachable; safe to retry
Every error is a JSON problem with a stable code. See Errors.
Confirm a checkout session
Section titled “Confirm a checkout session”POST /v1/checkout/sessions/{id}/confirm
Submits the customer’s choice of channel and their account, and creates the charge.
THERE IS NO AMOUNT IN THIS REQUEST. There is no currency and no fee bearer either. All three were fixed on the session by your secret key and are read back out of it here, so a page you do not fully control — or one an attacker has modified — cannot change what is charged.
IDEMPOTENT WITHOUT A HEADER. A customer double-tapping, or a page reloading while the prompt is on their handset, cannot produce two charges: a conditional UPDATE decides which request proceeds and the charge is created under a key derived from the session id. A second confirmation of a session that is already done returns the FIRST one’s result with 200, not an error. One arriving while another is genuinely in flight gets 409; retry in a second.
A 202-like outcome is normal: most payments come back pending or awaiting_action and complete asynchronously. Poll GET /v1/checkout/sessions/{id} or wait for your webhook.
An expired session is refused here on its own authority, not when a sweeper gets round to it.
Authentication: a publishable key (safe in a browser) with the scope payment_session.confirm.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
X-Paysigna-Client-Token |
string | yes | The client token your server received when it created the session. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The payment session’s id, which your server hands to the checkout page with the client token. |
Request body
Section titled “Request body”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
bank |
string | no | at most 30 characters | The bank’s code. Required for bank unless bank_id is sent instead. |
bank_id |
string (uuid) | no | The bank’s id from /v1/reference/banks. An alternative to bank; send one, not both. | |
channel |
string | yes | One of: mobile_money, bank, card |
Must be one of the channels the session lists. |
customer |
object | yes | ||
customer.email |
string (email) | no | at most 255 characters; e.g. [email protected] |
Used only when the session has no email. A card payment needs one. |
customer.identifier |
string | no | at most 100 characters | The customer’s mobile-money number or bank account. Optional for card, where the email is used. |
customer.name |
string | no | at most 150 characters; e.g. Ama Mensah |
The payer’s name. Takes precedence over a name pre-filled on the session. |
momo_network |
string | no | at most 30 characters; e.g. mtn |
The network’s code, such as mtn, telecel or airteltigo. Required for mobile_money unless momo_network_id is sent instead. |
momo_network_id |
string (uuid) | no | The network’s id from /v1/reference/momo-networks. An alternative to momo_network; send one, not both. |
curl -X POST https://sandbox.paysigna.com/v1/checkout/sessions/{id}/confirm \ -H "Authorization: Bearer sk_test_…" \ -H "Content-Type: application/json" \ -d '{ "channel": "mobile_money", "customer": { "email": "[email protected]", "name": "Ama Mensah" }, "momo_network": "mtn"}'Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
amount |
integer | In minor units. Read-only: this endpoint cannot change it and neither can confirming. |
channels |
array | The channels this session accepts. Confirming with anything else is refused. |
currency |
string | ISO 4217 code of amount. |
description |
string | What the payment is for, to show the customer. |
display_name |
string | Who the customer is paying. |
expires_at |
string (date-time) | After this the session cannot be paid. Create a new one. |
id |
string | The session’s id. |
object |
string | |
payment |
object | |
payment.action |
object | |
payment.action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
payment.action.expires_at |
string (date-time) | When the partner stops accepting this action, where it says. For otp, a code sent after this is refused; send a new one first if resendable. |
payment.action.message |
string | What to tell the customer, when the type alone does not say: for otp, where the code is (e-mailed to them, or shown in their bank’s app). Safe to show as is. |
payment.action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
payment.action.type |
string | prompt: the customer approves a prompt on their phone. otp: they enter a one-time code, which you send to POST /v1/charges/{id}/otp. 3ds_redirect or hosted_checkout: send them to url. ussd: they dial a code (reserved, not currently used). One of: prompt, otp, 3ds_redirect, hosted_checkout, ussd |
payment.action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
payment.amount |
integer | What the customer is actually debited, including any fee they bear. |
payment.completed_at |
string (date-time) | When the payment reached a final state. |
payment.customer_masked |
string | A partial identifier. The full value is never returned. |
payment.failure_message |
string | Safe to show the customer. PaySigna’s internal failure classification is not exposed. |
payment.reference |
string | Quote this to the merchant’s support, not to PaySigna. |
payment.status |
string | The payment’s state. Keep polling while it is initiated, pending, processing or awaiting_action; succeeded means paid; failed, cancelled or expired means nothing was collected. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired, reversed, refunded, partially_refunded |
return_url |
string | Where to send the customer when the flow ends. |
status |
string | created: waiting for the customer. confirming: a confirmation is in progress. completed: a payment was started; read payment for its result. cancelled or expired: the session can no longer be paid. One of: created, confirming, completed, cancelled, expired |
Errors
Section titled “Errors”400: the request is malformed401: the key is missing, wrong or revoked402: your balance cannot cover it403: the key lacks the scope, or your account may not do this404: no such resource on this account409: it conflicts with the current state, or the idempotency key was used for a different request422: a field is invalid;errorsnames it429: too many requests; wait forRetry-After500: Internal Server Error502: the payment partner refused it503: the payment partner is unreachable; safe to retry504: the partner timed out; read the resource before retrying
Every error is a JSON problem with a stable code. See Errors.
Submit a customer’s one-time code
Section titled “Submit a customer’s one-time code”POST /v1/checkout/sessions/{id}/otp
When payment.action.type is otp, send the code the customer received. The answer is the session, as GET returns it. The payment may ask for a SECOND code (an enrolment code first, then the payment code): keep asking while payment.action.type is otp.
A wrong code is a 422 otp_rejected and the payment keeps waiting; payment.action.attempts_remaining says how many tries are left. After the last one the payment fails (otp_attempts_exceeded). Two submissions at once reach the partner once: the second gets 409 request_in_progress.
Authentication: a publishable key (safe in a browser) with the scope payment_session.confirm.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
X-Paysigna-Client-Token |
string | yes | The client token your server received when it created the session. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The payment session’s id. |
Request body
Section titled “Request body”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
code |
string | yes | at most 12 characters | The code the customer received. |
curl -X POST https://sandbox.paysigna.com/v1/checkout/sessions/{id}/otp \ -H "Authorization: Bearer sk_test_…" \ -H "Content-Type: application/json" \ -d '{ "code": "string"}'Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
amount |
integer | In minor units. Read-only: this endpoint cannot change it and neither can confirming. |
channels |
array | The channels this session accepts. Confirming with anything else is refused. |
currency |
string | ISO 4217 code of amount. |
description |
string | What the payment is for, to show the customer. |
display_name |
string | Who the customer is paying. |
expires_at |
string (date-time) | After this the session cannot be paid. Create a new one. |
id |
string | The session’s id. |
object |
string | |
payment |
object | |
payment.action |
object | |
payment.action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
payment.action.expires_at |
string (date-time) | When the partner stops accepting this action, where it says. For otp, a code sent after this is refused; send a new one first if resendable. |
payment.action.message |
string | What to tell the customer, when the type alone does not say: for otp, where the code is (e-mailed to them, or shown in their bank’s app). Safe to show as is. |
payment.action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
payment.action.type |
string | prompt: the customer approves a prompt on their phone. otp: they enter a one-time code, which you send to POST /v1/charges/{id}/otp. 3ds_redirect or hosted_checkout: send them to url. ussd: they dial a code (reserved, not currently used). One of: prompt, otp, 3ds_redirect, hosted_checkout, ussd |
payment.action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
payment.amount |
integer | What the customer is actually debited, including any fee they bear. |
payment.completed_at |
string (date-time) | When the payment reached a final state. |
payment.customer_masked |
string | A partial identifier. The full value is never returned. |
payment.failure_message |
string | Safe to show the customer. PaySigna’s internal failure classification is not exposed. |
payment.reference |
string | Quote this to the merchant’s support, not to PaySigna. |
payment.status |
string | The payment’s state. Keep polling while it is initiated, pending, processing or awaiting_action; succeeded means paid; failed, cancelled or expired means nothing was collected. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired, reversed, refunded, partially_refunded |
return_url |
string | Where to send the customer when the flow ends. |
status |
string | created: waiting for the customer. confirming: a confirmation is in progress. completed: a payment was started; read payment for its result. cancelled or expired: the session can no longer be paid. One of: created, confirming, completed, cancelled, expired |
Errors
Section titled “Errors”400: the request is malformed401: the key is missing, wrong or revoked403: the key lacks the scope, or your account may not do this404: no such resource on this account409: it conflicts with the current state, or the idempotency key was used for a different request422: a field is invalid;errorsnames it429: too many requests; wait forRetry-After500: Internal Server Error502: the payment partner refused it503: the payment partner is unreachable; safe to retry504: the partner timed out; read the resource before retrying
Every error is a JSON problem with a stable code. See Errors.
Send the customer a new one-time code
Section titled “Send the customer a new one-time code”POST /v1/checkout/sessions/{id}/otp/resend
Only when payment.action.resendable is true; otherwise 422 unsupported. Attempts already used still count.
Authentication: a publishable key (safe in a browser) with the scope payment_session.confirm.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
X-Paysigna-Client-Token |
string | yes | The client token your server received when it created the session. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The payment session’s id. |
Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
amount |
integer | In minor units. Read-only: this endpoint cannot change it and neither can confirming. |
channels |
array | The channels this session accepts. Confirming with anything else is refused. |
currency |
string | ISO 4217 code of amount. |
description |
string | What the payment is for, to show the customer. |
display_name |
string | Who the customer is paying. |
expires_at |
string (date-time) | After this the session cannot be paid. Create a new one. |
id |
string | The session’s id. |
object |
string | |
payment |
object | |
payment.action |
object | |
payment.action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
payment.action.expires_at |
string (date-time) | When the partner stops accepting this action, where it says. For otp, a code sent after this is refused; send a new one first if resendable. |
payment.action.message |
string | What to tell the customer, when the type alone does not say: for otp, where the code is (e-mailed to them, or shown in their bank’s app). Safe to show as is. |
payment.action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
payment.action.type |
string | prompt: the customer approves a prompt on their phone. otp: they enter a one-time code, which you send to POST /v1/charges/{id}/otp. 3ds_redirect or hosted_checkout: send them to url. ussd: they dial a code (reserved, not currently used). One of: prompt, otp, 3ds_redirect, hosted_checkout, ussd |
payment.action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
payment.amount |
integer | What the customer is actually debited, including any fee they bear. |
payment.completed_at |
string (date-time) | When the payment reached a final state. |
payment.customer_masked |
string | A partial identifier. The full value is never returned. |
payment.failure_message |
string | Safe to show the customer. PaySigna’s internal failure classification is not exposed. |
payment.reference |
string | Quote this to the merchant’s support, not to PaySigna. |
payment.status |
string | The payment’s state. Keep polling while it is initiated, pending, processing or awaiting_action; succeeded means paid; failed, cancelled or expired means nothing was collected. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired, reversed, refunded, partially_refunded |
return_url |
string | Where to send the customer when the flow ends. |
status |
string | created: waiting for the customer. confirming: a confirmation is in progress. completed: a payment was started; read payment for its result. cancelled or expired: the session can no longer be paid. One of: created, confirming, completed, cancelled, expired |
Errors
Section titled “Errors”400: the request is malformed401: the key is missing, wrong or revoked403: the key lacks the scope, or your account may not do this404: no such resource on this account409: it conflicts with the current state, or the idempotency key was used for a different request422: a field is invalid;errorsnames it429: too many requests; wait forRetry-After500: Internal Server Error502: the payment partner refused it503: the payment partner is unreachable; safe to retry504: the partner timed out; read the resource before retrying
Every error is a JSON problem with a stable code. See Errors.

