Developer documentation
bitPay REST API
Integrate checkout and wallet transfers into your application. All endpoints use JSON and require a dashboard access token plus project API keys.
Base URL: https://api.bitpaygh.com/api/v1
Overview
A typical integration flow:
- Create a BitPay account and a project with callback and webhook URLs.
- Generate a personal API access token under API access in your dashboard.
- Generate project API keys on the project's API keys page.
- Call
POST /payments/initializeand redirect the customer toauthorization_url. - On return, call
GET /payments/verify/{reference}or handle signed webhooks.
Supported currencies: GHS.
For wallet payouts to customers, see Transfers.
Authentication
Every payment request requires three credentials. Access tokens are created only from your dashboard — there is no public token API.
| Header | Description |
|---|---|
| Authorization | Bearer {access_token} from dashboard → API access |
| X-Project-Public-Key | Project public key (pk_…) |
| X-Project-Secret-Key | Project secret key (sk_…) |
Also send Accept: application/json and
Content-Type: application/json on POST requests.
Create an account or sign in to generate credentials.
Initialize payment
POST https://api.bitpaygh.com/api/v1/payments/initialize
Creates a transaction and returns a checkout URL. Store data.reference for verify and webhooks.
Request body
{
"amount": 100.00,
"currency": "GHS",
"email": "customer@example.com",
"customer_name": "John Doe",
"callback_url": "https://your-app.com/payments/callback",
"metadata": {
"order_id": "ORD-2026-001"
}
}
| Field | Required | Notes |
|---|---|---|
| amount | Yes | Number > 0 |
| currency | Yes | GHS |
| Yes | Customer email | |
| customer_name | No | — |
| callback_url | No | Overrides project default; live requires HTTPS |
| metadata | No | Returned in webhooks |
Optional: Idempotency-Key — same key and body within 24h returns the original response.
Success response (201)
{
"status": true,
"message": "Payment initialized successfully",
"data": {
"reference": "PGW_TXN_01JXYZABCDEF123456",
"authorization_url": "https://checkout.example.com/...",
"access_code": "access_code_abc123",
"amount": "100.00",
"currency": "GHS",
"status": "pending"
}
}
Example
curl -X POST "https://api.bitpaygh.com/api/v1/payments/initialize" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-Project-Public-Key: pk_test_xxxxxxxx" \
-H "X-Project-Secret-Key: sk_test_xxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"amount":100,"currency":"GHS","email":"customer@example.com"}'
USSD/mobile money collection
POST https://api.bitpaygh.com/api/v1/payments/ussd/collect
Creates a mobile money payment request for USSD or server-side flows where the customer cannot be redirected to a checkout link. This endpoint does not return an authorization URL. Use the verify endpoint or webhooks to track the final status.
Required header: Idempotency-Key. Reusing the same key and body returns the original response.
Request body
{
"amount": 50,
"currency": "GHS",
"customer_name": "John Doe",
"customer_phone": "233597990630",
"network": "MTN",
"description": "Payment for order ORD-12345",
"metadata": {
"order_id": "ORD-12345"
}
}
| Field | Required | Notes |
|---|---|---|
| amount | Yes | Number >= 0.01 |
| currency | Yes | GHS only |
| customer_name | Yes | Customer name shown in your records |
| customer_phone | Yes | Mobile money number |
| network | Yes | MTN, AT, or TELECEL |
| description | No | Customer-facing payment description |
| metadata | No | Returned in webhooks |
Success response (201)
{
"status": true,
"message": "Payment request created successfully",
"data": {
"reference": "PGW_TXN_01JXYZABCDEF123456",
"status": "pending",
"amount": 50,
"currency": "GHS",
"customer": {
"name": "John Doe",
"phone": "233597990630"
},
"network": "MTN",
"instructions": "Complete the payment prompt on your phone."
}
}
Example
curl -X POST "https://api.bitpaygh.com/api/v1/payments/ussd/collect" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-Project-Public-Key: pk_test_xxxxxxxx" \
-H "X-Project-Secret-Key: sk_test_xxxxxxxx" \
-H "Idempotency-Key: unique-request-key" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"amount":50,"currency":"GHS","customer_name":"John Doe","customer_phone":"233597990630","network":"MTN"}'
Verify payment
GET https://api.bitpaygh.com/api/v1/payments/verify/{reference}
Confirms status with the gateway and updates the transaction. Use on your callback page; prefer webhooks for server-side finality.
Success response (200)
{
"status": true,
"message": "Payment verified successfully",
"data": {
"reference": "PGW_TXN_01JXYZABCDEF123456",
"status": "success",
"amount": "100.00",
"currency": "GHS",
"fee": "2.50",
"net_amount": "97.50",
"paid_at": "2026-05-16T14:30:00+00:00",
"customer": {
"email": "customer@example.com",
"name": "John Doe"
}
}
}
data.status may also be
pending, failed, or abandoned.
Transfers
Send money from your project wallet to a customer bank account or mobile money wallet. Transfers are processed automatically — no manual approval is required.
The recipient receives the full requested amount; the transfer fee is charged separately from your wallet
(total_debit = amount + fee).
Typical flow: list supported providers → create a recipient → create a transfer (optionally confirm OTP) → poll status or handle webhooks.
List supported banks and providers
GET https://api.bitpaygh.com/api/v1/transfers/banks
Optional query: type=bank or type=mobile_money.
{
"status": true,
"message": "Supported banks retrieved.",
"data": {
"banks": [
{
"name": "GT Bank",
"code": "058",
"type": "bank",
"currency": "GHS"
}
]
}
}
Create transfer recipient
POST https://api.bitpaygh.com/api/v1/transfers/recipients
Validates the account with BitPay and returns a TRP_ reference. Store this reference for transfer requests.
{
"recipient_type": "bank",
"account_number": "0123456789",
"account_name": "John Doe",
"bank_code": "058",
"currency": "GHS"
}
| Field | Required | Notes |
|---|---|---|
| recipient_type | Yes | bank or mobile_money |
| account_number | Yes | Recipient account or wallet number |
| bank_code | Yes | Code from GET /transfers/banks |
| account_name | No | Resolved automatically when possible |
| currency | No | Defaults to GHS |
{
"status": true,
"message": "Transfer recipient created.",
"data": {
"reference": "TRP_01JXYZABCDEF123456",
"recipient_type": "bank",
"account_name": "JOHN DOE",
"account_number": "••••••6789",
"bank_code": "058",
"bank_name": "GT Bank",
"currency": "GHS",
"status": "verified",
"verified_at": "2026-06-15T12:00:00+00:00",
"created_at": "2026-06-15T12:00:00+00:00"
}
}
List transfer recipients
GET https://api.bitpaygh.com/api/v1/transfers/recipients
Optional query: per_page (max 100, default 25).
Create transfer
POST https://api.bitpaygh.com/api/v1/transfers
Debits your project wallet by total_debit and sends amount to the recipient.
If OTP is enabled on the project, the transfer enters awaiting_otp until confirmed.
{
"recipient": "TRP_01JXYZABCDEF123456",
"amount": 100.00,
"currency": "GHS",
"metadata": {
"payout_id": "PAY-2026-001"
}
}
| Field | Required | Notes |
|---|---|---|
| recipient | Yes | TRP_ reference owned by this project |
| amount | Yes | Number > 0; amount sent to recipient |
| currency | No | GHS only |
| metadata | No | Returned in webhooks |
Optional: Idempotency-Key — same key and body within 24h returns the original response; same key with a different body returns 409.
{
"status": true,
"message": "Transfer created.",
"data": {
"reference": "TRF_01JXYZABCDEF123456",
"amount": "100.00",
"fee": "8.50",
"total_debit": "108.50",
"currency": "GHS",
"status": "processing",
"requires_otp": false,
"otp_verified_at": null,
"failure_reason": null,
"recipient": {
"reference": "TRP_01JXYZABCDEF123456",
"recipient_type": "bank",
"account_name": "JOHN DOE",
"account_number": "••••••6789",
"bank_name": "GT Bank"
},
"initiated_at": "2026-06-15T12:05:00+00:00",
"processed_at": "2026-06-15T12:05:01+00:00",
"completed_at": null,
"created_at": "2026-06-15T12:05:00+00:00"
}
}
Status values: pending,
awaiting_otp,
processing,
success,
failed,
reversed,
cancelled.
Confirm transfer OTP
POST https://api.bitpaygh.com/api/v1/transfers/{reference}/confirm-otp
Required when the project has Require OTP for transfers enabled. A one-time code is sent to the project owner by email (and SMS when configured).
{
"otp": "123456"
}
{
"status": true,
"message": "Transfer confirmed and queued for processing.",
"data": {
"reference": "TRF_01JXYZABCDEF123456",
"status": "pending",
"otp_verified_at": "2026-06-15T12:06:00+00:00"
}
}
Get transfer
GET https://api.bitpaygh.com/api/v1/transfers/{reference}
Returns the same transfer object shape as the create response.
Transfer webhooks
When your project webhook URL is set, BitPay sends signed POST requests for transfer lifecycle events using the same headers as payment webhooks.
transfer.pendingtransfer.awaiting_otptransfer.processingtransfer.successtransfer.failedtransfer.reversed
{
"event": "transfer.success",
"data": {
"reference": "TRF_01JXYZABCDEF123456",
"status": "success",
"amount": "100.00",
"fee": "8.50",
"total_debit": "108.50",
"currency": "GHS",
"recipient": {
"reference": "TRP_01JXYZABCDEF123456",
"type": "bank",
"account_name": "JOHN DOE",
"account_number": "••••••6789",
"bank_name": "GT Bank"
},
"failure_reason": null,
"metadata": {
"payout_id": "PAY-2026-001"
},
"completed_at": "2026-06-15T12:05:30+00:00"
}
}
Example
curl -X POST "https://api.bitpaygh.com/api/v1/transfers" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-Project-Public-Key: pk_test_xxxxxxxx" \
-H "X-Project-Secret-Key: sk_test_xxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: transfer-ord-001" \
-d '{"recipient":"TRP_01JXYZABCDEF123456","amount":100,"currency":"GHS"}'
Webhooks
Set a webhook URL on your project. BitPay sends a signed POST when a payment or transfer reaches a notable state.
Payment events
payment.successpayment.failedpayment.abandonedpayment.reversedpayment.refundedpayment.disputed
Transfer events
transfer.pendingtransfer.awaiting_otptransfer.processingtransfer.successtransfer.failedtransfer.reversed
See Transfers for request/response shapes and a transfer webhook payload example.
Headers
- X-Platform-Event — event name
- X-Platform-Timestamp — Unix timestamp
- X-Platform-Signature — HMAC-SHA256 hex digest
Verify with your project webhook signing secret (whsec_… from the project dashboard):
signature = HMAC_SHA256(secret, timestamp + "." + raw_json_body)
Payload example
{
"event": "payment.success",
"data": {
"reference": "PGW_TXN_01JXYZABCDEF123456",
"status": "success",
"amount": "120.00",
"currency": "GHS",
"fee": "6.00",
"net_amount": "114.00",
"customer": {
"email": "customer@example.com",
"name": "John Doe"
},
"metadata": {
"order_id": "ORD-2026-001"
},
"paid_at": "2026-05-16T14:30:00+00:00"
}
}
Respond with 2xx quickly. Failed deliveries are retried with backoff.
Errors
Errors return status: false and an HTTP status code.
{
"status": false,
"message": "The given data was invalid.",
"errors": {
"amount": ["The amount field is required."]
}
}
| HTTP | Meaning |
|---|---|
| 401 | Missing or invalid authentication |
| 403 | No project access or inactive business/project |
| 404 | Resource not found (e.g. payment or transfer reference) |
| 409 | Idempotency conflict |
| 422 | Validation failed |
| 429 | Rate limit exceeded |
| 502 | Payment gateway error |