Charges
Take a payment from a customer by mobile money, bank or card.
| Endpoint | What it does |
|---|---|
POST /v1/charges |
Create a charge |
GET /v1/charges/{id} |
Retrieve a charge |
POST /v1/charges/{id}/otp |
Submit a charge’s one-time code |
POST /v1/charges/{id}/otp/resend |
Re-send a charge’s one-time code |
Try any of them in the API tester.
Create a charge
Section titled “Create a charge”POST /v1/charges
Collects money from a customer over mobile money, bank or card.
Requires an Idempotency-Key header. Resending the same key with the same body replays the first response rather than charging again, so a retry after a timeout is always safe.
A 202-like outcome is normal: most charges return pending or awaiting_action and complete asynchronously. Wait for the webhook or poll the charge; do not treat the absence of succeeded as a failure.
Authentication: a secret key (server-side only) with the scope collection.create.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
Idempotency-Key |
string | yes | Unique per operation. Resend the same value to retry safely. |
Request body
Section titled “Request body”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
amount |
integer | yes | min 1; e.g. 1250 |
Amount in minor units — 1250 is GHS 12.50. |
bank |
string | no | at most 30 characters | The bank’s code. Required for bank unless bank_id is sent instead. Codes are listed at /v1/reference/banks. |
bank_id |
string (uuid) | no | The bank’s id from /v1/reference/banks. An alternative to bank; send one, not both. | |
callback_url |
string (uri) | no | HTTPS only. | |
channel |
string | yes | One of: mobile_money, bank, card |
How the customer pays: mobile_money (an approval prompt on their phone), bank (a debit from their bank account) or card (the customer enters their card on a hosted page). Your account must be enabled for the channel. |
currency |
string | yes | exactly 3 characters; e.g. GHS |
The currency of amount, as an ISO 4217 code such as GHS. Supported currencies are listed at /v1/reference/currencies. |
customer |
object | yes | ||
customer.email |
string (email) | no | at most 255 characters; e.g. [email protected] |
Required for card: the partner’s checkout and receipt go to it. |
customer.id |
string (uuid) | no | An existing PaySigna customer id, to record the payment against them. identifier (or email for card) is still required. | |
customer.identifier |
string | no | at most 100 characters; e.g. 0244123456 |
Required for mobile_money (the number: 9 to 15 digits, at most 20 characters as sent) and bank (the account number). Optional for card, where the email is used: the card itself is entered on the partner’s page. |
customer.name |
string | no | at most 150 characters; e.g. Ama Mensah |
The payer’s name. Saved on the customer record and returned as customer.name. |
description |
string | no | at most 500 characters; e.g. Order 1042 |
What the payment is for. Stored on the transaction and sent with the payment request; depending on the channel the customer may see it. |
fee_bearer |
string | no | One of: merchant, customer |
Only honoured if your account permits per-transaction choice. |
metadata |
object | no | Returned unchanged on every read and webhook. | |
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. Codes are listed at /v1/reference/momo-networks. |
momo_network_id |
string (uuid) | no | The network’s id from /v1/reference/momo-networks. An alternative to momo_network; send one, not both. | |
reference |
string | no | at most 100 characters | Your own identifier for this payment. |
return_url |
string (uri) | no | Where to send the customer after a redirect flow. |
curl -X POST https://sandbox.paysigna.com/v1/charges \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "amount": 1250, "channel": "mobile_money", "currency": "GHS", "customer": { "email": "[email protected]", "identifier": "0244123456", "name": "Ama Mensah" }, "description": "Order 1042", "momo_network": "mtn"}'Response 201
Section titled “Response 201”| Field | Type | What it is |
|---|---|---|
action |
object | |
action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
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. |
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. |
action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
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 |
action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
amount |
integer | The amount requested, in minor units. |
channel |
string | How the customer pays: mobile_money, bank or card. |
created_at |
string (date-time) | When the charge was created. |
currency |
string | ISO 4217 code of every amount on this charge. |
customer |
object | |
customer.masked |
string | A partial identifier. The full value is never returned. |
customer.name |
string | The payer’s name, when one was given. |
customer_amount |
integer | What the customer is charged, including any fee they bear. |
expires_at |
string (date-time) | After this, the charge expires and cannot complete. |
fee |
object | |
fee.bearer |
string | Who pays the fees: merchant (deducted from merchant_amount) or customer (added to customer_amount). One of: merchant, customer |
fee.commission |
integer | Commission you accepted for a company that referred you, on this payment. Follows bearer, like platform. |
fee.platform |
integer | PaySigna’s processing fee, in minor units. |
fee.tax |
integer | Tax charged on the fee, in minor units. |
id |
string | The charge’s id. Use it to read the charge, refund it, or find it in /v1/transactions. |
merchant_amount |
integer | What will be credited to your balance. |
metadata |
object | The metadata you sent, unchanged. |
object |
string | |
reference |
string | PaySigna’s reference. Quote it to support. |
status |
string | initiated: created, not yet sent. pending or processing: in progress. awaiting_action: the customer must act (see action). succeeded: the money was collected. failed, cancelled or expired: nothing was collected. Wait for the webhook or read the charge for the final state. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired |
your_reference |
string | The reference you sent when creating the charge. |
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 this409: 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.
Retrieve a charge
Section titled “Retrieve a charge”GET /v1/charges/{id}
Reads a charge’s current state. This is the authority after a timeout: if a create request did not return, read the charge rather than resending it.
Authentication: a secret key (server-side only) with the scope collection.read.
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The charge’s id, as returned when it was created. |
Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
action |
object | |
action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
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. |
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. |
action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
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 |
action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
amount |
integer | The amount requested, in minor units. |
channel |
string | How the customer pays: mobile_money, bank or card. |
created_at |
string (date-time) | When the charge was created. |
currency |
string | ISO 4217 code of every amount on this charge. |
customer |
object | |
customer.masked |
string | A partial identifier. The full value is never returned. |
customer.name |
string | The payer’s name, when one was given. |
customer_amount |
integer | What the customer is charged, including any fee they bear. |
expires_at |
string (date-time) | After this, the charge expires and cannot complete. |
fee |
object | |
fee.bearer |
string | Who pays the fees: merchant (deducted from merchant_amount) or customer (added to customer_amount). One of: merchant, customer |
fee.commission |
integer | Commission you accepted for a company that referred you, on this payment. Follows bearer, like platform. |
fee.platform |
integer | PaySigna’s processing fee, in minor units. |
fee.tax |
integer | Tax charged on the fee, in minor units. |
id |
string | The charge’s id. Use it to read the charge, refund it, or find it in /v1/transactions. |
merchant_amount |
integer | What will be credited to your balance. |
metadata |
object | The metadata you sent, unchanged. |
object |
string | |
reference |
string | PaySigna’s reference. Quote it to support. |
status |
string | initiated: created, not yet sent. pending or processing: in progress. awaiting_action: the customer must act (see action). succeeded: the money was collected. failed, cancelled or expired: nothing was collected. Wait for the webhook or read the charge for the final state. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired |
your_reference |
string | The reference you sent when creating the charge. |
Errors
Section titled “Errors”401: 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.
Submit a charge’s one-time code
Section titled “Submit a charge’s one-time code”POST /v1/charges/{id}/otp
Completes a charge whose action.type is otp: send the code the customer entered. action.message says where the customer finds it.
The answer is the charge as it now stands. Usually processing, settling like any other charge; it can also ask for ANOTHER code (action.type still otp, with a new action.message), as when a first payment from a bank account is preceded by a one-time enrolment. Ask the customer again and send the new code here.
A code the bank refuses is a 422 with otp_rejected, and nothing changes: the customer can try again while action.attempts_remaining lasts. At the limit the charge fails and the answer is otp_attempts_exceeded. A 409 means the charge is not waiting for a code, or a code for it is already being checked.
Requires an Idempotency-Key header. In the sandbox, a bank charge to an account number ending 6000 asks for a code and one ending 6001 asks for an enrolment code first; the code 123456 is accepted and any other is refused.
Authentication: a secret key (server-side only) with the scope collection.create.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
Idempotency-Key |
string | yes | Unique per code. Resend the same value to retry safely; a retry replays the first answer rather than sending the code again. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The charge’s id. |
Request body
Section titled “Request body”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
code |
string | yes | at least 4 characters; at most 12 characters; e.g. 123456 |
The one-time code the customer entered, as typed. Never stored or logged. |
curl -X POST https://sandbox.paysigna.com/v1/charges/{id}/otp \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "code": "123456"}'Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
action |
object | |
action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
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. |
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. |
action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
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 |
action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
amount |
integer | The amount requested, in minor units. |
channel |
string | How the customer pays: mobile_money, bank or card. |
created_at |
string (date-time) | When the charge was created. |
currency |
string | ISO 4217 code of every amount on this charge. |
customer |
object | |
customer.masked |
string | A partial identifier. The full value is never returned. |
customer.name |
string | The payer’s name, when one was given. |
customer_amount |
integer | What the customer is charged, including any fee they bear. |
expires_at |
string (date-time) | After this, the charge expires and cannot complete. |
fee |
object | |
fee.bearer |
string | Who pays the fees: merchant (deducted from merchant_amount) or customer (added to customer_amount). One of: merchant, customer |
fee.commission |
integer | Commission you accepted for a company that referred you, on this payment. Follows bearer, like platform. |
fee.platform |
integer | PaySigna’s processing fee, in minor units. |
fee.tax |
integer | Tax charged on the fee, in minor units. |
id |
string | The charge’s id. Use it to read the charge, refund it, or find it in /v1/transactions. |
merchant_amount |
integer | What will be credited to your balance. |
metadata |
object | The metadata you sent, unchanged. |
object |
string | |
reference |
string | PaySigna’s reference. Quote it to support. |
status |
string | initiated: created, not yet sent. pending or processing: in progress. awaiting_action: the customer must act (see action). succeeded: the money was collected. failed, cancelled or expired: nothing was collected. Wait for the webhook or read the charge for the final state. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired |
your_reference |
string | The reference you sent when creating the charge. |
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.
Re-send a charge’s one-time code
Section titled “Re-send a charge’s one-time code”POST /v1/charges/{id}/otp/resend
Asks the bank to send the code again, when action.resendable is true: a code sent by e-mail or SMS. A code the customer reads from their bank’s app cannot be re-sent, and the answer is 422. The answer is the charge; its action may carry a new expires_at.
Requires an Idempotency-Key header.
Authentication: a secret key (server-side only) with the scope collection.create.
Headers
Section titled “Headers”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
Idempotency-Key |
string | yes | Unique per resend. A retry with the same key does not send another code. |
Parameters
Section titled “Parameters”| Field | Type | Required | Allowed values and limits | What it is |
|---|---|---|---|---|
{id} |
string (uuid) | yes | The charge’s id. |
Response 200
Section titled “Response 200”| Field | Type | What it is |
|---|---|---|
action |
object | |
action.attempts_remaining |
integer | otp only: how many more incorrect codes are allowed before the charge fails. |
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. |
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. |
action.resendable |
boolean | otp only: the code can be sent to the customer again with POST /v1/charges/{id}/otp/resend. |
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 |
action.url |
string | Open this to complete authentication. Absent for a handset prompt. |
amount |
integer | The amount requested, in minor units. |
channel |
string | How the customer pays: mobile_money, bank or card. |
created_at |
string (date-time) | When the charge was created. |
currency |
string | ISO 4217 code of every amount on this charge. |
customer |
object | |
customer.masked |
string | A partial identifier. The full value is never returned. |
customer.name |
string | The payer’s name, when one was given. |
customer_amount |
integer | What the customer is charged, including any fee they bear. |
expires_at |
string (date-time) | After this, the charge expires and cannot complete. |
fee |
object | |
fee.bearer |
string | Who pays the fees: merchant (deducted from merchant_amount) or customer (added to customer_amount). One of: merchant, customer |
fee.commission |
integer | Commission you accepted for a company that referred you, on this payment. Follows bearer, like platform. |
fee.platform |
integer | PaySigna’s processing fee, in minor units. |
fee.tax |
integer | Tax charged on the fee, in minor units. |
id |
string | The charge’s id. Use it to read the charge, refund it, or find it in /v1/transactions. |
merchant_amount |
integer | What will be credited to your balance. |
metadata |
object | The metadata you sent, unchanged. |
object |
string | |
reference |
string | PaySigna’s reference. Quote it to support. |
status |
string | initiated: created, not yet sent. pending or processing: in progress. awaiting_action: the customer must act (see action). succeeded: the money was collected. failed, cancelled or expired: nothing was collected. Wait for the webhook or read the charge for the final state. One of: initiated, pending, processing, awaiting_action, succeeded, failed, cancelled, expired |
your_reference |
string | The reference you sent when creating the charge. |
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 retry
Every error is a JSON problem with a stable code. See Errors.

