# Authentication
Source: https://docs.mavapay.co/api-reference/authentication
All API endpoints are authenticated using API keys.
### Authentication Methods
All API requests are authenticated with an API key. You can find authentication requirements for each endpoint in the [OpenAPI specification file](https://github.com/stealthmoney/mavapay-docs/blob/main/api-reference/openapi.json).
Do not expose your API keys in public repositories. Use environment variables or a secure method to store and use your API keys.
We provision use RBAC for authentication. You can assign different roles to your API keys to control their access to the API. Roles can be `READ` or `WRITE`. You should keep your API key secure and never expose it in public repositories.
```json theme={null}
{
"api_key": "aktestsddkdkd"
}
```
If you suspect that your API key has been compromised, deactivate the key and regenerate a new one immediately from the [dashboard](https://mavapay.co/api-keys).
### Authorization Headers
When making requests to our API, include your API key in the `x-api-key` header:
```json API Key theme={null}
{
"x-api-key": ""
}
```
API requests made without a valid API key will return a `401: Unauthorized` status code.
# Get bank account details
Source: https://docs.mavapay.co/api-reference/endpoint/bank-account/get-account
get /bankaccount
Returns all your saved bank accounts for a given currency. The bank account details used to make payouts to your customers.
# Register a bank account
Source: https://docs.mavapay.co/api-reference/endpoint/bank-account/register-account
post /bankaccount
Register a bank account that can be used for withdrawal for a given currency. Only NGN is supported at the moment.
# Get bank code
Source: https://docs.mavapay.co/api-reference/endpoint/bank-info/bank-code
get /bank/bankcode
Returns all bank codes for a given country. This endpoint is currently limited to Nigeria(NGN) and South Africa(ZAR) only.
# Name Enquiry
Source: https://docs.mavapay.co/api-reference/endpoint/bank-info/name-enquiry
get /bank/name-enquiry
Verify bank account details if it is correct. The response returned should match the account number and account name of the user. This endpoint is currently limited to Nigeria(NGN) only
# Generate a stablecoin deposit address
Source: https://docs.mavapay.co/api-reference/endpoint/collection/generate-stablecoin-address
post /collections/stablecoin/generate-address
Generate a unique USDT deposit address on a supported network. Send this address to customers so they can deposit USDT into your Mavapay stablecoin wallet. You can generate multiple addresses per network and use `label` to identify them.
# List stablecoin deposit addresses
Source: https://docs.mavapay.co/api-reference/endpoint/collection/get-stablecoin-addresses
get /collections/stablecoin/addresses
Retrieve all previously generated USDT deposit addresses for your account. Filter by currency using the `currency` query parameter.
# Get all orders
Source: https://docs.mavapay.co/api-reference/endpoint/order/get-all-orders
get /order/all
Returns all orders
# Get order details
Source: https://docs.mavapay.co/api-reference/endpoint/order/get-order-by-id
get /order
Returns the order details for a given order ID
# Create a payment link
Source: https://docs.mavapay.co/api-reference/endpoint/payment-link/create-payment-link
post /paymentlink
Create a hosted payment link for collecting payments. Payment links simplify the checkout process by creating a hosted payment page where users can complete payments.
# Get payment link details
Source: https://docs.mavapay.co/api-reference/endpoint/payment-link/get-payment-link-details
get /paymentlink/details
Retrieve details about a payment link for rendering a custom UI or checking payment status.
# Get payment link orders
Source: https://docs.mavapay.co/api-reference/endpoint/payment-link/get-payment-link-orders
get /paymentlink/orders
Retrieve orders associated with a payment link. Useful for getting the order ID for payment simulation in staging.
# Get price ticker for currency pair
Source: https://docs.mavapay.co/api-reference/endpoint/price/get-price
get /price/ticker
Returns the exchange rate for a given currency pair
# Create a quote
Source: https://docs.mavapay.co/api-reference/endpoint/quote/get-quote
post /quote
Create a quote for a given currency
# Validate KES identifier
Source: https://docs.mavapay.co/api-reference/endpoint/quote/validate-kes-identifier
post /quote/validate-kes-identifier
Validate a Kenyan M-Pesa PayBill or Till number and retrieve the associated business name. This endpoint is only available in production.
# Get all transactions
Source: https://docs.mavapay.co/api-reference/endpoint/transaction/get-all-transactions
get /transactions
Returns all transactions with pagination and filtering options
# Get transaction details
Source: https://docs.mavapay.co/api-reference/endpoint/transaction/get-transaction
get /transaction
Returns the transaction details for a given transaction ID or transactions associated with an order
# Get user profile
Source: https://docs.mavapay.co/api-reference/endpoint/user/get-user-profile
get /user
Returns an exhaustive user profile detail
# Accept Swap Quote
Source: https://docs.mavapay.co/api-reference/endpoint/wallet/accept-quote
post /wallet/payout/{quoteId}/accept
Accept a quote to perform an internal currency swap
# Get wallet details
Source: https://docs.mavapay.co/api-reference/endpoint/wallet/get-wallets
get /wallet
Returns the wallet details for a given wallet ID or all wallets
# Register Webhook
Source: https://docs.mavapay.co/api-reference/endpoint/webhook/register-webhook
Register a webhook endpoint to receive event notifications
# Register Webhook
Register a webhook endpoint to receive notifications for transaction events.
## API Endpoint
```
POST https://api.mavapay.co/api/v1/webhook/register
```
## Request Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------ |
| url | string | Yes | The URL where webhook events will be sent |
| secret | string | Yes | A secret key used to sign webhook payloads |
## Example Request
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/webhook/register \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key_here" \
-d '{
"url": "https://your-domain.com/webhook",
"secret": "your_webhook_secret"
}'
```
## Example Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "wh_123456789",
"url": "https://your-domain.com/webhook",
"isActive": true,
"createdAt": "2024-03-21T15:30:00Z"
}
}
```
For more information about webhooks, see our [webhook documentation](/webhooks/markdown).
# Update Webhook
Source: https://docs.mavapay.co/api-reference/endpoint/webhook/update-webhook
Update an existing webhook configuration
# Update Webhook
Update an existing webhook configuration with new URL or secret.
## API Endpoint
```
PUT https://api.mavapay.co/api/v1/webhook
```
## Request Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------- |
| url | string | Yes | The new URL where webhook events will be sent |
| secret | string | Yes | A new secret key used to sign webhook payloads |
## Example Request
```bash theme={null}
curl -X PUT https://api.mavapay.co/api/v1/webhook \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key_here" \
-d '{
"url": "https://your-new-domain.com/webhook",
"secret": "your_new_webhook_secret"
}'
```
## Example Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "wh_123456789",
"url": "https://your-new-domain.com/webhook",
"isActive": true,
"updatedAt": "2024-03-21T15:30:00Z"
}
}
```
For more information about webhooks, see our [webhook documentation](/webhooks/markdown).
# Withdraw BTC
Source: https://docs.mavapay.co/api-reference/endpoint/withdraw/withdraw-btc
post /withdraw/btc
Withdraw BTC to a wallet
# Withdraw fiat
Source: https://docs.mavapay.co/api-reference/endpoint/withdraw/withdraw-fiat
post /withdraw
Withdraw funds to a bank account. The amount is specified in the lowest denomination of the currency (e.g., Kobo for NGN). You must first verify the bank account using the Name Enquiry endpoint before initiating a withdrawal.
# Withdraw stablecoin
Source: https://docs.mavapay.co/api-reference/endpoint/withdraw/withdraw-stablecoin
post /withdraw/stablecoin
Withdraw USDT from your Mavapay stablecoin wallet to an external address. The amount is specified in USDT cents (2 decimal places). For example, `100` = 1 USDT.
# Errors
Source: https://docs.mavapay.co/api-reference/errors
Common errors and response codes
## Error Codes
Mavapay API uses conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the `2xx` range indicate success, codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted), and codes in the `5xx` range indicate an error with Mavapay's servers.
### Common Error Codes
Here are some common error codes you might encounter when using the Mavapay API:
* `400 Bad Request`: The request was invalid.
* `401 Unauthorized`: The request was not authorized.
* `403 Forbidden`: The request was forbidden.
* `404 Not Found`: The resource was not found.
* `405 Method Not Allowed`: The request method is not allowed.
* `429 Too Many Requests`: You have exceeded the rate limit.
* `500 Internal Server Error`: An error occurred on the server.
### Error Response
When an error occurs, the API will return an error response in JSON format. The response will contain an error message and a status code. Here is an example of an error response:
If you encounter an error, check the error message for more information on what went wrong. If you need further assistance, please contact our [support](mailto:info@mavapay.co) team.
```json theme={null}
{
"status": "error",
"message": "Invalid API key provided."
}
```
# Introduction
Source: https://docs.mavapay.co/api-reference/introduction
Learn how to integrate our APIs into your application.
Before you start, you need to create a [free Mavapay
account](/authentication) and get your test API keys to
start building with the Mavapay API. All monies transacted in the sandbox
environment are not real and are for testing purposes only.
## About the API
The Mavapay API is a set of RESTful APIs that allow you to interact with our platform. We provide APIs for payments, transfers, and more. You can use our APIs to build amazing payment experiences for your customers.
We follow the [OpenAPI](https://swagger.io/specification/) specification for our API documentation.
Our base URL for the sandbox/staging API is
`https://staging.api.mavapay.co/api/v1` while the base URL for the live API is
`https://api.mavapay.co/api/v1`.
### HTTP Methods
Our API endpoints support the following HTTP methods:
* `GET`: Retrieve data from the server.
* `POST`: Send data to the server.
* `PUT`: Update data on the server.
* `DELETE`: Remove data from the server.
### Status Codes
Our API uses standard HTTP status codes to indicate the success or failure of an API request. Here are some common status codes you might encounter:
* `200 OK`: The request was successful.
* `201 Created`: The resource was created successfully.
* `400 Bad Request`: The request was invalid.
* `401 Unauthorized`: The request was not authorized.
* `404 Not Found`: The resource was not found.
* `500 Internal Server Error`: An error occurred on the server.
### Sample requests
We provide sample requests for each API endpoint in our documentation. You can use these samples to understand how to interact with our APIs.
### Requests and Responses
All requests to our API must include a valid API key in the `Authorization` header. Responses from our API are in JSON format. Content-Type should be set to `application/json`.
The response body will contain the data you requested or an error message if the request was unsuccessful. Here is an example of a successful response:
```json theme={null}
{
"status": "ok",
"message": "User retrieved successfully.",
"data": {
"id": 1,
"name": "satoshi nakamoto"
}
}
```
#### keys
* `status`: The status of the request (`success` or `error`).
* `message`: A message describing the status of the request.
* `data`: The data returned by the API. Always an object or an array.
### Supported Currencies
Mavapay makes use of the [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) format for currency codes. When sending currency codes in requests, please use the appropriate ISO 4217 code for the currency you are working with.
> When sending amounts, please ensure that the amount is in the smallest unit of the currency. For example, if you are sending 100 NGN, you should send `10000` as the amount `(100 * 100)`. For USDT, amounts are in cents (2 decimal places), so 1 USDT is `100`.
>
> Here are the list of currencies our API supports (we're constantly adding more currencies):
```json NGN theme={null}
{
"currency code": "NGN",
"currency name": "Nigerian Naira",
"symbol": "NGN",
"base unit": "kobo"
}
```
```json KES theme={null}
{
"currency code": "KES",
"currency name": "Kenyan Shilling",
"symbol": "KES",
"base unit": "cent"
}
```
```json ZAR theme={null}
{
"currency code": "ZAR",
"currency name": "South African Rand",
"symbol": "R",
"base unit": "cent"
}
```
```json GHS theme={null}
{
"currency code": "GHS",
"currency name": "Ghanaian Cedi",
"symbol": "₵",
"base unit": "pesewa"
}
```
```json BTC theme={null}
{
"currency": "BTC",
"currency name": "Bitcoin",
"symbol": "B",
"base unit": "satoshis"
}
```
```json USDT theme={null}
{
"currency": "USDT",
"currency name": "Tether USD",
"symbol": "USDT",
"base unit": "cent (1 USDT = 100 cents)"
}
```
# Authentication
Source: https://docs.mavapay.co/authentication
Set up your account and start building with the Mavapay API.
### Step 1: Create an account
Create a [free Mavapay account](https://mavapay.co). By default, you should use the [test environment](https://staging.mavapay.co) until you are done with the integration and are ready to go live.
### Step 2: Complete compliance (Production)
After creating your account on `https://mavapay.co`, complete the compliance section before API key generation is enabled.
### Step 3: Generate your API key
Once your compliance submission is approved, go to the API Keys section and generate your API key.
These keys are a 32 character string. You will need them to authenticate your requests to the Mavapay API.
Here's a sample of what your API keys will look like:
```json theme={null}
{
"apiKey": "e2a81dcbcc459e3c96c15fe88db653051949058d908"
}
```
### Step 4: Start building
You can now start building with the Mavapay API.
In the staging environment, you would be able to:
1. Collect payments using mocked card data
2. Make transfers to dummy accounts (bank accounts and mobile money wallets).
3. Build with our wallets (using mock data), including the USDT wallet for deposits and withdrawals.
4. Configure your integration to handle different scenarios like failed payments, successful payments, etc.
5. Complete KYC verification and go live with your integration.
On sandbox (`https://staging.mavapay.co`), compliance approval is instant for testing, so you do not need to fill the compliance form before generating test API keys.
# Development
Source: https://docs.mavapay.co/development
Learn how to integrate with Mavapay API in a test environment
## Environment Overview
Mavapay provides two distinct environments for your integration:
### Staging Environment
The staging environment is designed for development and testing. Key characteristics:
* Base URL: `https://staging.api.mavapay.co/api/v1`
* Uses Bitcoin's Signet network for Lightning Network transactions
* All Lightning invoices are test invoices (prefixed with `lntbs`)
* Generated coins are not spendable or transferable
* Simulated bank transfers and mobile money transactions
* No real funds involved
* Test API keys required
* Webhook notifications work identically to production
### Production Environment
The production environment is for live transactions:
* Base URL: `https://api.mavapay.co/api/v1`
* Uses the main Bitcoin Lightning Network
* Real funds are involved
* Requires completed KYC verification
* Live bank transfers and mobile money transactions
* Production API keys required
Never use production API keys in staging or staging API keys in production. Always test thoroughly in staging before moving to production.
## Getting Started with Staging
### Prerequisites
1. Create a sandbox [Mavapay account](https://staging.mavapay.co/create-account)
2. Get your [test API keys](/authentication)
3. Complete basic account setup
### Testing Features
#### Lightning Network Payments
* All invoices are generated on Bitcoin's Signet network
* Test invoices are prefixed with `lntbs`
* Example test invoice:
```text theme={null}
lntbs64740n1pney8wspp538tl6et3ez7lgfpqjx2n257zvg5d6kmk09ef6g927txgfuqq6x7q
```
#### Bank Transfers
Test different scenarios with:
* Mock bank account numbers
* Various response states (success/failure)
* Different currencies (NGN, KES, ZAR)
#### Stablecoins (USDT)
Test USDT deposits and withdrawals on staging:
* Generate deposit addresses on supported networks (`trc20`, `erc20`, `plasma`, `solana`, `polygon`)
* Withdraw USDT from your test wallet to an external address
* Receive the same `payment.received` and `payment.sent` webhooks as production
See the [stablecoins guide](/stablecoins/introduction) for the full deposit and withdraw flow.
#### Mobile Money
Test mobile money features using:
* Test phone numbers
* Simulated provider responses
* All supported providers (M-PESA, etc.)
### Error Scenarios
The staging environment allows testing various error cases:
* Insufficient funds
* Network timeouts
* Rate limit exceeded
* Invalid API key
* Invalid request
## Webhooks in Staging
Webhook notifications work identically to production:
1. Configure your webhook URL in the [API reference](/api-reference/endpoint/webhook/register-webhook)
2. Receive notifications for all test transactions
3. Test your webhook handling code
Use tools like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) for local webhook testing.
## Moving to Production
Before going live:
1. Complete all integration testing in staging
2. Verify webhook handling
3. Complete KYC verification
4. Request production API keys
5. Update API endpoints to production URLs
6. Test with small amounts initially
## Support
Need help during development?
* Check our [API Reference](/api-reference)
* Join our [Discord community](https://discord.gg/w82z7DdTM9)
* Email support at [info@mavapay.co](mailto:info@mavapay.co)
The staging environment is reset periodically. Don't rely on staging data persistence for long-term testing.
# Beneficiary Formats
Source: https://docs.mavapay.co/guides/beneficiary-formats
Required beneficiary object formats for each supported currency
When creating a quote, you must provide beneficiary information in the correct format for the target currency. This guide shows the required format for each supported currency.
## Bitcoin (BTC)
For Bitcoin payouts, you can use one of three methods: Lightning Invoice, Lightning Address, or On-Chain Address.
### Lightning Invoice
```json theme={null}
{
"beneficiary": {
"lnInvoice": "lntbs20u1p5dmy87pp5l2pm..."
}
}
```
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------------- |
| `lnInvoice` | string | Yes | Lightning Network invoice for the payout |
The invoice must be valid and not expired when the payout is processed.
### Lightning Address
```json theme={null}
{
"beneficiary": {
"lnAddress": "user@wallet.com"
}
}
```
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------------- |
| `lnAddress` | string | Yes | Lightning address (email-like format) |
### On-Chain Address
For on-chain Bitcoin payouts to a standard Bitcoin address:
```json theme={null}
{
"beneficiary": {
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5"
}
}
```
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | ------------------------------------------------ |
| `onChainAddress` | string | Yes | Bitcoin address (legacy, P2SH, or native SegWit) |
On-chain transactions have a minimum amount of **6000 SATs** and require a minimum of 3 blockchain confirmations. For faster, lower-fee payments, consider using Lightning.
## Nigerian Naira (NGN)
For Nigerian Naira payouts, provide complete bank account details.
```json theme={null}
{
"beneficiary": {
"bankAccountName": "olaolu olajide",
"bankCode": "000013",
"bankName": "GTBANK PLC",
"bankAccountNumber": "0087241555"
}
}
```
### Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ---------------------------------------------------- |
| `bankAccountName` | string | Yes | Account holder's name as registered with the bank |
| `bankCode` | string | Yes | NIP bank code (get from `/bank/bankcode?country=NG`) |
| `bankName` | string | Yes | Full bank name |
| `bankAccountNumber` | string | Yes | 10-digit Nigerian bank account number |
Use the [Name Enquiry endpoint](/api-reference/endpoint/bank-info/name-enquiry) to validate the account details before creating a quote.
## South African Rand (ZAR)
For South African Rand payouts, provide the beneficiary name and bank details.
```json theme={null}
{
"beneficiary": {
"name": "test merchant 1",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}
```
### Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | --------------------------------- |
| `name` | string | Yes | Beneficiary's full name |
| `bankName` | string | Yes | South African bank name |
| `bankAccountNumber` | string | Yes | South African bank account number |
Bank account validation is not currently available for ZAR. Ensure account details are correct before creating a quote.
## Kenyan Shilling (KES)
Kenyan payouts support mobile money (M-Pesa and Airtel) and M-Pesa paybill/till payments. There are three supported payment methods:
### Pay to Phone (Mobile Money)
Send money directly to a mobile phone number on M-Pesa or Airtel Mobile Money.
```json theme={null}
{
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "John Doe",
"network": "mpesa"
}
}
}
```
#### Fields
| Field | Type | Required | Description |
| ------------------------- | ------ | -------- | ----------------------------------------------------- |
| `identifierType` | string | Yes | Must be `"paytophone"` |
| `identifiers.phoneNumber` | string | Yes | Kenyan phone number in international format (+254...) |
| `identifiers.accountName` | string | Yes | Name associated with the mobile money account |
| `identifiers.network` | string | Yes | Target telecom network: `"mpesa"` or `"airtel"` |
Starting **July 12, 2026**, the `network` field is **required** for all pay-to-phone KES quotes.
### Pay to Bill (Business Payments)
Send money to a business paybill number with an account reference.
```json theme={null}
{
"beneficiary": {
"identifierType": "paytobill",
"identifiers": {
"accountName": "vlad",
"paybillNumber": "4141031",
"accountNumber": "vlad"
}
}
}
```
#### Fields
| Field | Type | Required | Description |
| --------------------------- | ------ | -------- | ----------------------------------------------------- |
| `identifierType` | string | Yes | Must be `"paytobill"` |
| `accountName` | string | Yes | Account name (usually the same as the account number) |
| `identifiers.paybillNumber` | string | Yes | M-Pesa paybill number |
| `identifiers.accountNumber` | string | Yes | Account number/reference for the paybill |
You can validate the paybill business name using the [KES Identifier Validation endpoint](/api-reference/endpoint/quote/validate-kes-identifier). See [detailed documentation](#kes-identifier-validation) below.
### Pay to Till
Send money to a business till number.
```json theme={null}
{
"beneficiary": {
"identifierType": "paytotill",
"accountName": "Business Name",
"identifiers": {
"tillNumber": "796980711"
}
}
}
```
#### Fields
| Field | Type | Required | Description |
| ------------------------ | ------ | -------- | --------------------- |
| `identifierType` | string | Yes | Must be `"paytotill"` |
| `accountName` | string | Yes | Name of the business |
| `identifiers.tillNumber` | string | Yes | M-Pesa till number |
You can validate the till business name using the [KES Identifier Validation endpoint](/api-reference/endpoint/quote/validate-kes-identifier). See [detailed documentation](#kes-identifier-validation) below.
All Kenyan phone numbers must be in international format starting with +254.
## KES Identifier Validation
For Pay to Bill and Pay to Till payment methods, you can validate the business name before creating a quote. This endpoint is only available in production.
### Endpoint
```
POST https://api.mavapay.co/api/v1/quote/validate-kes-identifier
```
### Headers
| Header | Value |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |
| `x-api-key` | Your API key |
### Request Body
For Paybill validation:
```json theme={null}
{
"identifier": "247247",
"identifierType": "PayBill"
}
```
For Till validation:
```json theme={null}
{
"identifier": "796980711",
"identifierType": "Till"
}
```
### Parameters
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | -------------------------------------- |
| `identifier` | string | Yes | The paybill or till number to validate |
| `identifierType` | string | Yes | Either `"PayBill"` or `"Till"` |
### Response
```json theme={null}
{
"status": "ok",
"data": {
"organization_name": "Equity Paybill Account"
}
}
```
### Example: Validate Paybill
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote/validate-kes-identifier' \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"identifier": "247247",
"identifierType": "PayBill"
}'
```
### Example: Validate Till
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote/validate-kes-identifier' \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"identifier": "796980711",
"identifierType": "Till"
}'
```
This validation endpoint is only available in the production environment.
## Complete Quote Examples
### Example: NGN Payout
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"bankAccountName": "olaolu olajide",
"bankCode": "000013",
"bankName": "GTBANK PLC",
"bankAccountNumber": "0087241555"
}
}
```
### Example: KES Payout (Pay to Phone)
```json theme={null}
{
"amount": "100000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "John Doe",
"network": "mpesa"
}
}
}
```
### Example: ZAR Payout
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "ZARCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "ZARCENT",
"autopayout": true,
"beneficiary": {
"name": "test merchant 1",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}
```
### Example: BTC Payout (Lightning Invoice)
```json theme={null}
{
"amount": "2000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"lnInvoice": "lntbs20u1p5dmy87pp5l2pm..."
}
}
```
### Example: BTC Payout (Lightning Address)
```json theme={null}
{
"amount": "2000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"lnAddress": "user@getalby.com"
}
}
```
### Example: BTC Payout (On-Chain Address)
```json theme={null}
{
"amount": "2000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"onChainAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
}
}
```
## Validation
Before creating a quote, you can validate beneficiary details:
* **NGN**: Use the [Name Enquiry endpoint](/api-reference/endpoint/bank-info/name-enquiry) to validate account details
* **ZAR**: Validation not currently available
* **KES**:
* For Pay to Phone: Ensure phone numbers are in correct format (+254...) and include the required `network` field (`"mpesa"` or `"airtel"`)
* For Pay to Bill and Pay to Till: Use the [KES Identifier Validation endpoint](#kes-identifier-validation) to validate business names (production only)
* **BTC**: Ensure Lightning invoice is valid and not expired
## Next Steps
* View [complete quote examples](/api-reference/quote-examples) for all currency pairs
* Learn about [webhook notifications](/webhooks/introduction) for tracking payments
* Test your integration in our [staging environment](https://staging.api.mavapay.co/api/v1)
# Buying Bitcoin (Lightning)
Source: https://docs.mavapay.co/guides/buying-bitcoin
Complete guide to implementing Bitcoin purchase functionality with fiat currencies via Lightning Network
This guide covers buying Bitcoin via **Lightning Network** (instant, low fees). For on-chain Bitcoin purchases, see [Buying Bitcoin (On-Chain)](/guides/buying-bitcoin-onchain).
## Overview
Mavapay allows your users to buy Bitcoin using Nigerian Naira (NGN). The API provides flexible options for how amounts are specified and how Bitcoin is delivered.
**Two Bitcoin Delivery Options:**
* **Internal Wallet** (`autopayout: false`): Bitcoin is credited to your Mavapay wallet. The `beneficiary` field is ignored. You control when and how to deliver Bitcoin to your users.
* **Instant Delivery** (`autopayout: true`): Bitcoin is automatically sent to the provided Lightning invoice immediately after payment is received.
## Use Cases
1. **Fixed Fiat Amount**: User wants to spend exactly ₦5,000
2. **Fixed Bitcoin Amount**: User wants to receive exactly 10,000 sats
3. **Instant Delivery**: Bitcoin sent immediately to user's Lightning wallet
## Understanding Payment Currency
The `paymentCurrency` parameter is crucial for buy Bitcoin flows. It determines what the `amount` field represents:
* **`paymentCurrency: "NGNKOBO"`** → amount is in Naira (you're specifying how much to spend)
* **`paymentCurrency: "BTCSAT"`** → amount is in Bitcoin (you're specifying how much to receive)
This allows you to support both "I want to spend ₦5,000" and "I want to buy 10,000 sats" use cases with the same API.
## Integration Flow
### Step 1: Create a Quote
Choose one of two approaches based on your use case:
#### Option A: User Specifies Fiat Amount
When a user wants to spend exactly ₦5,000:
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/quote \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"amount": "500000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": false
}'
```
The response tells you how much Bitcoin they'll receive.
#### Option B: User Specifies Bitcoin Amount
When a user wants to receive exactly 10,000 sats:
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/quote \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"amount": "10000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": false
}'
```
The response tells you how much Naira they need to pay.
See the [Create Quote API reference](/api-reference/endpoint/quote/get-quote) for interactive examples you can test.
### Step 2: Display Payment Instructions
Show the user the bank account details from the quote response:
```javascript theme={null}
const quote = response.data;
// Display to user
const paymentInstructions = {
bank: quote.bankName, // e.g., "GLOBUS BANK"
accountNumber: quote.ngnBankAccountNumber, // e.g., "3242273802"
accountName: quote.ngnAccountName, // e.g., "Mava Digital Solutions Limited"
amount: quote.amountInSourceCurrency / 100, // Convert kobo to Naira
expiresAt: quote.expiry,
orderId: quote.orderId
};
```
**Example UI:**
```
Please transfer ₦8,445.40 to:
Bank: GLOBUS BANK
Account: 3242273802
Name: Mava Digital Solutions Limited
Order ID: 43477-4306
Quote expires in: 14:32
```
Quotes expire after approximately 10 minutes for bank transfers and 5 minutes for lightning invoice payments. Display a countdown timer and prompt users to complete payment quickly.
### Step 3: Wait for Payment Confirmation
You'll receive a `payment.received` webhook when the user makes the bank transfer:
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "9830a229-1db0-4a19-9e51-37cca51586f2",
"amount": 3427,
"currency": "BTC",
"type": "DEPOSIT",
"status": "SUCCESS",
"orderId": "58772-8830",
...
}
}
```
See [Webhook Examples](/webhooks/webhook-examples#bitcoin-btc-webhooks) for complete payload details.
### Step 4: Credit User's Account
You have two options for delivering Bitcoin:
#### Manual Payout (autopayout: false)
* Bitcoin is credited to your Mavapay wallet
* **The `beneficiary` field is ignored** when `autopayout` is false
* You handle crediting user's account in your system
* Later withdraw to user's wallet using the [Withdraw BTC API](/api-reference/endpoint/withdraw/withdraw-btc)
#### Auto Payout (autopayout: true)
* Bitcoin is automatically sent to the provided Lightning invoice
* You receive both `payment.received` and `payment.sent` webhooks
* User receives Bitcoin within seconds
## Auto Payout with Lightning Invoice
For instant Bitcoin delivery, set `autopayout: true` and provide a Lightning invoice:
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/quote \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"amount": "208238",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"lnInvoice": "lnbc2082380n1p5fhytm..."
}
}'
```
### Requirements for Autopayout
* `autopayout` can only be `true` when `paymentCurrency` equals the target currency (BTCSAT)
* A valid `beneficiary` with Lightning invoice or Lightning address must be provided
* For Lightning invoices: The invoice amount **must match** the `amount` field in the request
* For Lightning addresses: Amount matching is not required since the invoice is generated on the fly
### Webhooks Flow
1. **payment.received** - Naira payment confirmed
2. **payment.sent** - Bitcoin delivered to invoice
```json theme={null}
// First webhook
{
"event": "payment.received",
"data": {
"type": "DEPOSIT",
"currency": "BTC",
"status": "SUCCESS",
...
}
}
// Second webhook (a few seconds later)
{
"event": "payment.sent",
"data": {
"type": "WITHDRAWAL",
"currency": "BTC",
"status": "SUCCESS",
...
}
}
```
## Implementation Examples
### Example 1: Basic Buy Bitcoin (No Autopayout)
```javascript theme={null}
async function buyBitcoin(amountNaira) {
// 1. Create quote
const quote = await fetch('https://api.mavapay.co/api/v1/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.MAVAPAY_API_KEY
},
body: JSON.stringify({
amount: (amountNaira * 100).toString(), // Convert to kobo
sourceCurrency: 'NGNKOBO',
targetCurrency: 'BTCSAT',
paymentMethod: 'BANKTRANSFER',
paymentCurrency: 'NGNKOBO',
autopayout: false
})
});
const { data } = await quote.json();
// 2. Store quote and show bank details to user
await saveQuote(data.id, data.orderId);
return {
orderId: data.orderId,
bankName: data.bankName,
accountNumber: data.ngnBankAccountNumber,
accountName: data.ngnAccountName,
amount: data.amountInSourceCurrency / 100,
bitcoinAmount: data.amountInTargetCurrency,
expiry: data.expiry
};
}
// 3. Handle webhook
app.post('/webhook', async (req, res) => {
const { event, data } = req.body;
if (event === 'payment.received' && data.currency === 'BTC') {
// Credit user's BTC balance
await creditUserBitcoin(data.orderId, data.amount);
}
res.json({ received: true });
});
```
### Example 2: Instant Bitcoin Delivery
```javascript theme={null}
async function buyBitcoinInstant(satoshiAmount, userLightningInvoice) {
// Decode invoice to verify amount
const decoded = await decodeInvoice(userLightningInvoice);
if (decoded.satoshis !== satoshiAmount) {
throw new Error('Invoice amount does not match requested amount');
}
// Create quote with autopayout
const quote = await fetch('https://api.mavapay.co/api/v1/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.MAVAPAY_API_KEY
},
body: JSON.stringify({
amount: satoshiAmount.toString(),
sourceCurrency: 'NGNKOBO',
targetCurrency: 'BTCSAT',
paymentMethod: 'BANKTRANSFER',
paymentCurrency: 'BTCSAT',
autopayout: true,
beneficiary: {
lnInvoice: userLightningInvoice
}
})
});
const { data } = await quote.json();
return {
orderId: data.orderId,
bankDetails: {
bank: data.bankName,
account: data.ngnBankAccountNumber,
name: data.ngnAccountName
},
amountToPay: data.amountInSourceCurrency / 100,
bitcoinAmount: satoshiAmount,
deliveryMethod: 'Lightning (Instant)',
expiry: data.expiry
};
}
// Handle both webhooks
app.post('/webhook', async (req, res) => {
const { event, data } = req.body;
if (event === 'payment.received') {
await updateOrderStatus(data.orderId, 'payment_received');
await notifyUser(data.orderId, 'Payment received, sending Bitcoin...');
}
if (event === 'payment.sent') {
await updateOrderStatus(data.orderId, 'completed');
await notifyUser(data.orderId, 'Bitcoin delivered to your wallet!');
}
res.json({ received: true });
});
```
## Important Considerations
### Quote Expiration
Quotes are valid for approximately 10 minutes for bank transfers and 5 minutes for lightning invoice payments. If a user pays after expiration:
* Payment may be refunded
* Or applied manually by contacting support
* Always display a clear countdown timer
### Exchange Rate Volatility
Exchange rates update frequently. For the best rate:
* Create a fresh quote each time
* Don't cache quotes for long periods
* Explain to users that rates may change
### Amount Validation
When using autopayout with Lightning invoices:
```javascript theme={null}
// Always decode and verify invoice amount
const bolt11 = require('bolt11');
function validateInvoice(invoice, expectedSats) {
const decoded = bolt11.decode(invoice);
const invoiceAmount = decoded.satoshis;
if (invoiceAmount !== expectedSats) {
throw new Error(
`Invoice amount (${invoiceAmount} sats) does not match ` +
`expected amount (${expectedSats} sats)`
);
}
return true;
}
```
## Testing
Test your integration in the staging environment:
```
https://staging.api.mavapay.co/api/v1
```
Use the [Simulation endpoint](/guides/simulation) to test payment flows without real money:
```bash theme={null}
# After creating a quote
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"currency": "NGN",
"quoteId": "53a87b44-2ecc-4106-8297-f1fd15d010a9",
"amount": 844540
}'
```
## Best Practices
### 1. Show Real-Time Rates
Create new quotes to show current exchange rates:
```javascript theme={null}
// Good: Fresh quote every time
const quote = await createQuote(amount);
// Bad: Cached quote from 10 minutes ago
const quote = getCachedQuote();
```
### 2. Display Expiry Prominently
```javascript theme={null}
function CountdownTimer({ expiryDate }) {
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(expiryDate));
return (
Quote expires in: {timeLeft}
Pay quickly to lock in this rate
);
}
```
### 3. Handle Webhooks Idempotently
The same webhook may be sent multiple times:
```javascript theme={null}
async function handlePaymentReceived(data) {
const { id, orderId, amount } = data;
// Check if already processed
const existing = await db.transactions.findOne({ id });
if (existing) {
console.log(`Transaction ${id} already processed`);
return;
}
// Process and mark as complete
await db.transactions.insert({ id, orderId, amount, processed: true });
await creditUserAccount(orderId, amount);
}
```
### 4. Provide Status Updates
Keep users informed throughout the process:
```javascript theme={null}
const STATUS_MESSAGES = {
'quote_created': 'Please make payment to the account below',
'payment_received': 'Payment confirmed! Processing...',
'bitcoin_sent': 'Bitcoin delivered to your wallet',
'expired': 'Quote expired. Please create a new order.'
};
```
### 5. Handle Errors Gracefully
```javascript theme={null}
try {
const quote = await createQuote(amount);
} catch (error) {
if (error.message.includes('amount too small')) {
return 'Minimum purchase is ₦1,000';
}
if (error.message.includes('quote expired')) {
return 'Quote expired. Please try again.';
}
return 'Unable to create quote. Please try again.';
}
```
## Supported Currencies
Currently available:
* NGNKOBO => BTCSAT (Nigerian Naira to Bitcoin)
Coming soon:
* KESCENT => BTCSAT (Kenyan Shilling to Bitcoin)
* ZARCENT => BTCSAT (South African Rand to Bitcoin)
## Related Resources
* [Create Quote API](/api-reference/endpoint/quote/get-quote) - Interactive API documentation
* [Withdraw Bitcoin API](/api-reference/endpoint/withdraw/withdraw-btc) - Manual Bitcoin payouts
* [Webhook Examples](/webhooks/webhook-examples) - Complete webhook payloads
* [Beneficiary Formats](/guides/beneficiary-formats) - Beneficiary object structures
* [Payment Simulation](/guides/simulation) - Testing in staging
## Troubleshooting
### Quote Creation Fails
**Error: "Invalid payment currency"**
* Ensure `paymentCurrency` is either `sourceCurrency` or `targetCurrency`
* For buying BTC, use `NGNKOBO` or `BTCSAT`
**Error: "Amount too small"**
* Check minimum amounts in the API response
* Amounts are in the lowest denomination (kobo for NGN, satoshis for BTC)
### Autopayout Not Working
**Error: "Autopayout requires payment currency to be target currency"**
* Set `paymentCurrency: "BTCSAT"` when buying BTC with autopayout
* Provide a valid Lightning invoice in the `beneficiary` field
**Error: "Invoice amount mismatch"**
* The Lightning invoice amount must exactly match the `amount` field
* Decode invoice and verify before creating quote
### Payment Not Detected
* Ensure webhook is registered and publicly accessible
* Check that user sent the exact amount shown in the quote
* Verify payment was sent to the correct bank account
* Contact support with the `orderId` for manual verification
## FAQ
**Q: What's the minimum amount I can buy?**\
A: Minimum amounts vary. Check the API response for current limits.
**Q: How long does autopayout take?**\
A: Usually within seconds of receiving Naira or Bitcoin payment.
**Q: Can I use on-chain addresses instead of Lightning?**\
A: Yes! See [Buying Bitcoin (On-Chain)](/guides/buying-bitcoin-onchain) for on-chain integration with standard Bitcoin addresses.
**Q: What happens if the user pays after the quote expires?**\
A: The payment may be refunded or processed manually. Contact support with the order ID.
**Q: Can I set my own exchange rate markup?**\
A: Yes, use the `customerInternalFee` field to add your margin.
**Q: How do I handle partial payments?**\
A: Partial payments are not automatically processed. Contact support for manual handling.
# Buying Bitcoin (On-Chain)
Source: https://docs.mavapay.co/guides/buying-bitcoin-onchain
Complete guide to implementing on-chain Bitcoin purchase functionality with fiat currencies
## Overview
Mavapay allows your users to buy Bitcoin on-chain using Nigerian Naira (NGN). This guide covers two methods for on-chain Bitcoin purchases: **Quote Requests** and **Payment Links**.
**On-Chain vs Lightning:**
* **On-Chain**: Bitcoin is sent to a standard Bitcoin address. Transactions require block confirmations (slower, higher fees, but works with any Bitcoin wallet).
* **Lightning**: Bitcoin is sent via Lightning Network invoice or address (faster, lower fees). See [Buying Bitcoin (Lightning)](/guides/buying-bitcoin) for Lightning integration.
**Minimum Amount**: The minimum amount for on-chain transactions is **6000 SATs**.
## Method 1: Using Quote Request
Generate a quote to buy Bitcoin on-chain. This method allows you to lock in an exchange rate before the user makes payment.
### Key Concepts
| Parameter | Description |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `sourceCurrency` | The currency the user pays with (e.g., `NGNKOBO`) |
| `targetCurrency` | The currency to receive (e.g., `BTCSAT`) |
| `paymentCurrency` | Determines what `amount` represents - set to `BTCSAT` to specify exact Bitcoin amount, or `NGNKOBO` to specify exact fiat amount |
| `beneficiary.onChainAddress` | The Bitcoin address to receive the on-chain payment |
### Create a Quote
Request a quote specifying the on-chain address as the beneficiary:
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/quote \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"amount": "6000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"speed": "medium",
"autopayout": true,
"beneficiary": {
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5"
}
}'
```
### Quote Response
The response includes the amount to pay and bank details for the transfer:
```json theme={null}
{
"status": "ok",
"data": {
"id": "7166a7d2-0615-4e86-b3f8-e20a29807390",
"exchangeRate": 1,
"usdToTargetCurrencyRate": 0.000011556485210588052,
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"transactionFeesInSourceCurrency": 4458,
"transactionFeesInTargetCurrency": 30,
"amountInSourceCurrency": 820200,
"amountInTargetCurrency": 6000,
"paymentMethod": "BANKTRANSFER",
"expiry": "2025-11-20T20:05:02.652Z",
"isValid": true,
"invoice": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"hash": "691f721731f71c0012df3342",
"totalAmountInSourceCurrency": 820200,
"customerInternalFee": 0,
"bankName": "GLOBUS BANK",
"ngnBankAccountNumber": "3242454461",
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankCode": "000027",
"estimatedRoutingFee": 343,
"orderId": "54474-8334"
}
}
```
### Response Fields
| Field | Description |
| ------------------------ | ------------------------------------------ |
| `amountInSourceCurrency` | Amount to pay in kobo |
| `amountInTargetCurrency` | Bitcoin amount in satoshis |
| `bankName` | Bank name for the transfer |
| `ngnBankAccountNumber` | Account number for the transfer |
| `ngnAccountName` | Account name for the transfer |
| `estimatedRoutingFee` | Estimated on-chain network fee in satoshis |
| `orderId` | Order reference for tracking |
| `expiry` | Quote expiration time |
### Display Payment Instructions
Show the user the bank account details:
```javascript theme={null}
const quote = response.data;
const paymentInstructions = {
bank: quote.bankName,
accountNumber: quote.ngnBankAccountNumber,
accountName: quote.ngnAccountName,
amount: quote.amountInSourceCurrency / 100, // Convert kobo to Naira
bitcoinAmount: quote.amountInTargetCurrency,
networkFee: quote.estimatedRoutingFee,
expiresAt: quote.expiry,
orderId: quote.orderId
};
```
***
## Method 2: Using Payment Links
Payment Links simplify the checkout process by creating a hosted payment page. Users can complete payments without your application handling the bank transfer details directly.
View the complete API reference for creating and managing payment links.
### Key Constraints for On-Chain Payment Links
| Parameter | Requirement |
| -------------------- | ---------------------------------------- |
| `settlementMethod` | Must be `"ONCHAIN"` |
| `paymentMethods` | Only `["BANKTRANSFER"]` is supported |
| `speed` | Required - controls network fee priority |
| `settlementCurrency` | The target currency (e.g., `"BTC"`) |
| `paymentCurrency` | The source currency (e.g., `"NGN"`) |
### Speed Options
The `speed` parameter determines the on-chain transaction fee and confirmation time:
| Speed | Description |
| -------- | ------------------------------------ |
| `slow` | Lower fees, longer confirmation time |
| `medium` | Balanced fees and confirmation time |
| `fast` | Higher fees, faster confirmation |
### Create a Payment Link
```bash theme={null}
curl -X POST https://api.mavapay.co/api/v1/paymentlink \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"name": "Bitcoin Purchase",
"description": "On-chain Bitcoin payment",
"type": "One_Time",
"addFeeToTotalCost": false,
"settlementCurrency": "BTC",
"settlementMethod": "ONCHAIN",
"speed": "medium",
"paymentCurrency": "NGN",
"paymentMethods": ["BANKTRANSFER"],
"beneficiary": {
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5"
},
"amount": 800000,
"callbackUrl": "https://your-app.com/callback"
}'
```
### Payment Link Response
```json theme={null}
{
"status": "ok",
"message": "payment link created successfully",
"data": {
"paymentLink": "https://checkout.staging.mavapay.co/9d1c20db-c46b-4a15-8233-1bb999e38195",
"paymentRef": "9d1c20db-c46b-4a15-8233-1bb999e38195"
}
}
```
### Get Payment Link Details
Retrieve details about a payment link for rendering a custom UI:
```bash theme={null}
curl -X GET "https://api.mavapay.co/api/v1/paymentlink/details?id=9f8bf7da-f1d8-4c78-8f93-28eebe17c35e" \
-H "x-api-key: "
```
### Details Response
```json theme={null}
{
"status": "ok",
"message": "payment link details",
"data": {
"paymentLinkDetails": {
"id": "9f8bf7da-f1d8-4c78-8f93-28eebe17c35e",
"name": "Bitcoin Purchase",
"description": "On-chain Bitcoin payment",
"callbackUrl": "https://your-app.com/callback",
"settlementCurrency": "BTC",
"paymentLinkOrderId": "99366bb2-788f-4556-86db-d5063232a50a",
"paymentMethods": ["BANKTRANSFER"],
"account": {
"name": "Your Business Name",
"logo": ""
},
"BANKTRANSFER": {
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankName": "GLOBUS BANK",
"ngnBankAccountNumber": "3242569160",
"amount": 800000,
"expiresAt": "2025-11-20T20:47:41.528Z",
"targetAmount": 6034
}
}
}
}
```
***
## Testing & Simulation (Staging Only)
Test the on-chain flow in staging without real funds.
Simulation is only available in the **staging environment**: `https://staging.api.mavapay.co/api/v1`
### Step 1: Get the Order ID
For Payment Links, retrieve the order ID from the payment link orders endpoint:
```bash theme={null}
curl -X GET "https://staging.api.mavapay.co/api/v1/paymentlink/orders?id={paymentRef}" \
-H "x-api-key: "
```
The response includes the payment link details and all associated orders:
```json theme={null}
{
"status": "ok",
"message": "paymentlink order details",
"data": {
"id": "7e644e02-e652-42d8-8857-e8ae46bccd59",
"name": "Bitcoin Purchase",
"description": "On-chain Bitcoin payment",
"type": "ONE_TIME",
"amount": "800000",
"settlementCurrency": "BTC",
"settlementMethod": "ONCHAIN",
"speed": "medium",
"paymentCurrency": "NGN",
"paymentMethods": ["BANKTRANSFER"],
"status": "ACTIVE",
"beneficiaryId": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"orders": [
{
"id": "eb1377a8-5afd-4888-a702-d510d070466b",
"status": "EXPIRED",
"orderId": "49508-6205",
"paymentLinkId": "7e644e02-e652-42d8-8857-e8ae46bccd59",
"paymentMethod": "BANKTRANSFER",
"createdAt": "2026-01-09T12:36:25.924Z",
"updatedAt": "2026-01-09T12:47:10.254Z"
},
{
"id": "35a153c9-dff0-4cb5-a603-e494965ed6ad",
"status": "PENDING",
"orderId": "17454-1571",
"paymentLinkId": "7e644e02-e652-42d8-8857-e8ae46bccd59",
"paymentMethod": "BANKTRANSFER",
"createdAt": "2026-01-09T13:09:59.212Z",
"updatedAt": "2026-01-09T13:09:59.212Z"
}
]
}
}
```
**Order Expiration**: When an order expires, a new order is automatically created for the payment link. Use the `orderId` from the order with `status: "PENDING"` for simulation.
### Step 2: Simulate Payment
Use the order ID to simulate a bank transfer:
```bash theme={null}
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
-H "Content-Type: application/json" \
-H "x-api-key: " \
-d '{
"amount": 800000,
"currency": "NGN",
"orderId": "11110-0001"
}'
```
The `amount` is in the lowest denomination (kobo for NGN).
***
## Webhooks
After a successful payment, your registered webhook endpoint receives events tracking the transaction through completion.
### Event Flow
1. **`payment.received`** - Fiat payment received from user
2. **`payment.sent`** - Bitcoin sent on-chain (status: PENDING until 3 confirmations)
3. **`payment_link.settled`** - Payment link transaction fully settled
On-chain withdrawals remain in `PENDING` status until there are **3 block confirmations**, after which a success event is sent.
### payment\_link.settled
Sent when a Payment Link transaction is fully settled:
```json theme={null}
{
"event": "payment_link.settled",
"data": {
"paymentRef": "1c50bb6a-9f49-493c-86b5-75a9e790f7c9",
"paymentLink": "https://checkout.mavapay.co/1c50bb6a-9f49-493c-86b5-75a9e790f7c9",
"callbackUrl": "https://your-app.com/callback",
"settlementCurrency": "BTC",
"paymentCurrency": "BTC",
"paymentMethod": "BANKTRANSFER",
"transaction": {
"id": "220b23dc-38f7-413c-b2d1-b7f765c50add",
"walletId": "40aa2b67-1498-449d-85f2-17b93eab2d74",
"ref": "19d4793744af1e9fc77cb64d374d6",
"hash": "691f836d4e7ce000125478be",
"amount": 6000,
"fees": 0,
"currency": "BTC",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-11-20T21:10:12.180Z",
"updatedAt": "2025-11-20T23:35:52.986Z",
"btcUsdMetadata": {
"id": "b6d82314-0148-413e-bfad-ccd74a45969e",
"orderId": "44092-3881",
"paymentMethod": "BANKTRANSFER",
"externalRef": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"estimatedRoutingFee": 172,
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"lnInvoice": null,
"lnAddress": null,
"onchainTxid": "5c5df6db67a8fbe684d48560fe151a0ecfade509d640bd21b6a787707b972f62",
"onchainNetworkSpeed": "medium",
"createdAt": "2025-11-20T21:10:12.185Z",
"updatedAt": "2025-11-20T23:35:53.036Z"
}
}
}
}
```
### payment.sent (On-Chain Withdrawal)
Indicates the on-chain withdrawal has been processed:
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "220b23dc-38f7-413c-b2d1-b7f765c50add",
"walletId": "40aa2b67-1498-449d-85f2-17b93eab2d74",
"ref": "19d4793744af1e9fc77cb64d374d6",
"hash": "691f836d4e7ce000125478be",
"amount": 6000,
"fees": 0,
"currency": "BTC",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-11-20T21:10:12.180Z",
"updatedAt": "2025-11-20T23:35:52.986Z",
"btcUsdMetadata": {
"id": "b6d82314-0148-413e-bfad-ccd74a45969e",
"orderId": "44092-3881",
"paymentMethod": "BANKTRANSFER",
"externalRef": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"estimatedRoutingFee": 172,
"lnInvoice": null,
"lnAddress": null,
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"onchainTxid": "5c5df6db67a8fbe684d48560fe151a0ecfade509d640bd21b6a787707b972f62",
"onchainNetworkSpeed": "medium",
"createdAt": "2025-11-20T21:10:12.185Z",
"updatedAt": "2025-11-20T23:35:53.036Z"
}
}
}
```
### On-Chain Specific Fields
| Field | Description |
| --------------------- | ----------------------------------------------------------------- |
| `onChainAddress` | The Bitcoin address that received the payment |
| `onchainTxid` | The Bitcoin transaction ID (can be looked up on a block explorer) |
| `onchainNetworkSpeed` | The speed setting used (`slow`, `medium`, `fast`) |
### payment.received (Fiat Deposit)
Indicates the fiat deposit has been received:
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "83ce7294-9127-4714-8de8-d173b42228f5",
"walletId": "40aa2b67-1498-449d-85f2-17b93eab2d74",
"ref": "944499859bb3f3d10e84bd0248b1e",
"hash": "691f85b24e7ce00012547906",
"amount": 6017,
"fees": 31,
"currency": "BTC",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-11-20T21:20:24.474Z",
"updatedAt": "2025-11-20T21:20:24.474Z",
"btcUsdMetadata": {
"id": "997b6f65-b76c-4668-8722-7e97d37ccb3e",
"orderId": "25401-6132",
"externalRef": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"paymentMethod": "BANKTRANSFER",
"estimatedRoutingFee": 172,
"lnInvoice": null,
"lnAddress": null,
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"createdAt": "2025-11-20T21:10:12.185Z",
"updatedAt": "2025-11-20T23:35:53.036Z"
}
}
}
```
***
## Implementation Example
### Complete On-Chain Purchase Flow
```javascript theme={null}
async function buyBitcoinOnChain(satoshiAmount, userBitcoinAddress, speed = 'medium') {
// 1. Create quote with on-chain address
const quote = await fetch('https://api.mavapay.co/api/v1/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.MAVAPAY_API_KEY
},
body: JSON.stringify({
amount: satoshiAmount.toString(),
sourceCurrency: 'NGNKOBO',
targetCurrency: 'BTCSAT',
paymentMethod: 'BANKTRANSFER',
paymentCurrency: 'BTCSAT',
autopayout: true,
speed: speed,
beneficiary: {
onChainAddress: userBitcoinAddress
}
})
});
const { data } = await quote.json();
// 2. Store quote and show bank details to user
await saveQuote(data.id, data.orderId);
return {
orderId: data.orderId,
bankName: data.bankName,
accountNumber: data.ngnBankAccountNumber,
accountName: data.ngnAccountName,
amountNaira: data.amountInSourceCurrency / 100,
bitcoinSats: data.amountInTargetCurrency,
networkFee: data.estimatedRoutingFee,
expiry: data.expiry
};
}
// 3. Handle webhooks
app.post('/webhook', async (req, res) => {
const { event, data } = req.body;
switch (event) {
case 'payment.received':
await updateOrderStatus(data.btcUsdMetadata.orderId, 'payment_received');
await notifyUser('Payment received, sending Bitcoin on-chain...');
break;
case 'payment.sent':
if (data.btcUsdMetadata.onchainTxid) {
await updateOrderStatus(data.btcUsdMetadata.orderId, 'bitcoin_sent');
await notifyUser(`Bitcoin sent! TXID: ${data.btcUsdMetadata.onchainTxid}`);
}
break;
case 'payment_link.settled':
await updateOrderStatus(data.transaction.btcUsdMetadata.orderId, 'completed');
await notifyUser('Transaction confirmed on blockchain!');
break;
}
res.json({ received: true });
});
```
***
## Important Considerations
### Minimum Amount
On-chain transactions require a minimum of **6000 SATs** due to network dust limits and fees.
### Confirmation Time
On-chain transactions require blockchain confirmations:
* `slow`: \~60+ minutes (1+ blocks)
* `medium`: \~30-60 minutes
* `fast`: \~10-30 minutes
### Network Fees
The `estimatedRoutingFee` field shows the expected on-chain fee. Actual fees may vary based on network congestion.
### Address Validation
Always validate Bitcoin addresses before creating quotes. Use a proper library like `bitcoinjs-lib` to validate addresses with checksum verification:
```javascript theme={null}
import * as bitcoin from 'bitcoinjs-lib'; //make sure the bitcoinjs-lib is installed
function isValidBitcoinAddress(address) {
try {
bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin);
return true;
} catch (e) {
return false;
}
}
```
Avoid regex-based validation as it doesn't verify checksums and can accept invalid addresses. Always use a library that performs proper checksum validation.
***
## Related Resources
* [Buying Bitcoin (Lightning)](/guides/buying-bitcoin) - Lightning Network integration
* [Create Quote API](/api-reference/endpoint/quote/get-quote) - API reference
* [Create Payment Link API](/api-reference/endpoint/payment-link/create-payment-link) - Payment Link API reference
* [Beneficiary Formats](/guides/beneficiary-formats) - All beneficiary object formats
* [Webhook Examples](/webhooks/webhook-examples) - Complete webhook payloads
* [Payment Simulation](/guides/simulation) - Testing in staging
# Internal Currency Swap
Source: https://docs.mavapay.co/guides/internal-currency-swap
Learn how to perform an internal currency swap from NGN to BTC
This guide outlines how to perform an internal currency swap from **NGNKOBO** (Naira Kobo) to **BTCSAT** (Bitcoin Satoshis) using the Mavapay API. This process allows you to convert funds directly from your internal NGN wallet balance.
***
## **Internal Swap Flow: NGN to BTC**
Performing a swap is a two-step process: **Requesting a Quote** and **Accepting the Quote**.
### **Step 1: Create a Swap Quote**
The [quote endpoint](/api-reference/endpoint/quote/get-quote) calculates the exchange rate and fees. You must specify the amount in the lowest denomination (Kobo for NGN, Satoshis for BTC).
#### **Scenario A: Swapping a specific NGN amount**
If you want to spend exactly 2,000 NGN:
* **Amount:** `200000` (2,000 \* 100 kobo)
* **Payment Currency:** `NGNKOBO`
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"amount": "200000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": false
}'
```
#### **Scenario B: Swapping for a specific BTC amount**
If you need exactly 5,000 Satoshis:
* **Amount:** `5000`
* **Payment Currency:** `BTCSAT`
### **Step 2: The Quote Response**
Mavapay returns a quote object. Pay close attention to the `id` and the `amountInTargetCurrency`.
The rate for the swap is returned in the quote i.e. the `usdToTragetCurrencyRate`.
In the sample response below, you can get the USD/BTC and NGN/BTC rate by doing:
BTC/USD = 1 / `usdToTargetCurrencyRate`
NGNKOBO/BTCSAT = `amountInSourceCurrency` / `amountInTargetCurrency` (This is how much 1 SAT = x kobo).
> **Note:** Quotes are typically valid for **5–10 minutes**. You must settle before the `expiry` timestamp.
**Sample Response:**
```json theme={null}
{
"status": "ok",
"data": {
"id": "985e2de2-0227-4348-8852-a7f9e3820e7c",
"usdToTargetCurrencyRate": 0.000015188346171836677",
"amountInSourceCurrency": 200000,
"amountInTargetCurrency": 2142,
"expiry": "2026-02-12T17:21:29.371Z",
"isValid": true
// ... other fields
}
}
```
### **Step 3: Accept and Settle from Wallet**
To finalize the swap, call the [accept endpoint](/api-reference/endpoint/wallet/accept-quote). This will immediately deduct the `amountInSourceCurrency` from your **NGN Internal Wallet** and credit the BTC to your account.
* **Endpoint:** `.../wallet/payout/{quoteId}/accept`
* **Payload:** Specify `NGN` as the `sourceWallet`.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/wallet/payout/985e2de2-0227-4348-8852-a7f9e3820e7c/accept' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"sourceWallet": "NGN"
}'
```
***
## **Key Considerations**
* **Denominations:** Always use the lowest unit.
* **Sufficient Balance:** Ensure your internal NGN wallet has enough funds to cover the `totalAmountInSourceCurrency` (which includes fees).
* **Rate Volatility:** Because BTC prices change rapidly, the quote `id` is short-lived. If it expires, you must generate a new quote.
# Name Enquiry Guide
Source: https://docs.mavapay.co/guides/name-enquiry-guide
Complete guide to validating Nigerian bank account details before creating quotes
# Name Enquiry Implementation Guide
This guide shows you how to implement bank account validation for Nigerian Naira (NGN) payouts using Mavapay's Name Enquiry API.
Name enquiry is currently supported for **Nigerian Naira (NGN) accounts only**.
View the complete API documentation for the Name Enquiry endpoint
## Recommended Flow
Follow this flow to collect and validate beneficiary bank details:
### 1. Fetch List of Supported Banks
Get all Nigerian banks with their NIP bank codes using the [Get Bank Codes endpoint](/api-reference/endpoint/bank-info/bank-code).
```bash theme={null}
GET /api/v1/bank/bankcode?country=NG
```
**Response:**
```json theme={null}
{
"status": "ok",
"message": "All bank codes",
"data": [
{ "bankName": "ACCESS BANK", "nipBankCode": "000014" },
{ "bankName": "GUARANTY TRUST BANK", "nipBankCode": "000013" },
{ "bankName": "FIRST BANK OF NIGERIA", "nipBankCode": "000016" },
{ "bankName": "ZENITH BANK", "nipBankCode": "000015" },
{ "bankName": "UNITED BANK FOR AFRICA", "nipBankCode": "000004" },
{ "bankName": "KUDA MICROFINANCE BANK", "nipBankCode": "090267" },
...
]
}
```
### 2. Let User Select Their Bank
Present the list of banks to your user and let them select their bank. Store the bank name and NIP bank code.
```javascript theme={null}
// Example: Display banks in a dropdown
const banks = response.data.sort((a, b) =>
a.bankName.localeCompare(b.bankName)
);
// User selects: GUARANTY TRUST BANK (000013)
const selectedBank = {
name: "GUARANTY TRUST BANK",
code: "000013"
};
```
### 3. Collect Account Number
Ask the user to enter their 10-digit Nigerian bank account number.
```javascript theme={null}
const accountNumber = "0123456789"; // User input
```
### 4. Verify Account Details
Call the [Name Enquiry endpoint](/api-reference/endpoint/bank-info/name-enquiry) to get the account name.
```bash theme={null}
GET /api/v1/bank/name-enquiry?accountNumber=0123456789&bankCode=000013
```
**Response:**
```json theme={null}
{
"status": "ok",
"message": "Account name retrieved successfully",
"data": {
"accountName": "OLADEJI OLAOLU",
"accountNumber": "0123456789",
"bankCode": "000013"
}
}
```
### 5. Display for Confirmation
Show the retrieved account name to the user for confirmation.
```javascript theme={null}
// Display to user:
"Please confirm the account details:
Bank: GUARANTY TRUST BANK
Account Number: 0123456789
Account Name: OLADEJI OLAOLU
Is this correct? [Yes] [No]"
```
### 6. Create Quote with Validated Details
Once confirmed, use the validated details to [create a quote](/api-reference/endpoint/quote/get-quote).
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"bankAccountName": "OLADEJI OLAOLU",
"bankCode": "000013",
"bankName": "GUARANTY TRUST BANK",
"bankAccountNumber": "0123456789"
}
}
```
## Complete Implementation Example
### React/Next.js Example
```typescript theme={null}
import { useState } from 'react';
interface Bank {
bankName: string;
nipBankCode: string;
}
export function BankAccountForm() {
const [banks, setBanks] = useState([]);
const [selectedBank, setSelectedBank] = useState(null);
const [accountNumber, setAccountNumber] = useState('');
const [accountName, setAccountName] = useState('');
const [loading, setLoading] = useState(false);
// Step 1: Fetch banks
const fetchBanks = async () => {
const response = await fetch(
'https://api.mavapay.co/api/v1/bank/bankcode?country=NG',
{
headers: { 'X-API-KEY': process.env.MAVAPAY_API_KEY }
}
);
const data = await response.json();
setBanks(data.data.sort((a, b) =>
a.bankName.localeCompare(b.bankName)
));
};
// Step 4: Verify account
const verifyAccount = async () => {
if (!selectedBank || accountNumber.length !== 10) {
alert('Please select a bank and enter a valid account number');
return;
}
setLoading(true);
try {
const response = await fetch(
`https://api.mavapay.co/api/v1/bank/name-enquiry?accountNumber=${accountNumber}&bankCode=${selectedBank.nipBankCode}`,
{
headers: { 'X-API-KEY': process.env.MAVAPAY_API_KEY }
}
);
const data = await response.json();
if (data.status === 'ok') {
setAccountName(data.data.accountName);
} else {
alert('Failed to verify account. Please check the details.');
}
} catch (error) {
alert('Error verifying account');
} finally {
setLoading(false);
}
};
// Step 6: Create quote
const createQuote = async () => {
const response = await fetch(
'https://api.mavapay.co/api/v1/quote',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': process.env.MAVAPAY_API_KEY
},
body: JSON.stringify({
amount: "1000000",
sourceCurrency: "BTCSAT",
targetCurrency: "NGNKOBO",
paymentMethod: "LIGHTNING",
paymentCurrency: "NGNKOBO",
autopayout: true,
beneficiary: {
bankAccountName: accountName,
bankCode: selectedBank.nipBankCode,
bankName: selectedBank.bankName,
bankAccountNumber: accountNumber
}
})
}
);
const data = await response.json();
// Handle quote response...
};
return (
);
}
```
### Node.js/Express Example
```javascript theme={null}
const express = require('express');
const router = express.Router();
// Step 1: Get banks
router.get('/banks', async (req, res) => {
const response = await fetch(
'https://api.mavapay.co/api/v1/bank/bankcode?country=NG',
{
headers: { 'X-API-KEY': process.env.MAVAPAY_API_KEY }
}
);
const data = await response.json();
res.json(data);
});
// Step 4: Verify account
router.post('/verify-account', async (req, res) => {
const { accountNumber, bankCode } = req.body;
if (!accountNumber || accountNumber.length !== 10) {
return res.status(400).json({ error: 'Invalid account number' });
}
try {
const response = await fetch(
`https://api.mavapay.co/api/v1/bank/name-enquiry?accountNumber=${accountNumber}&bankCode=${bankCode}`,
{
headers: { 'X-API-KEY': process.env.MAVAPAY_API_KEY }
}
);
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: 'Verification failed' });
}
});
module.exports = router;
```
## API Endpoints Reference
For complete API documentation, see:
* [Get Bank Codes endpoint](/api-reference/endpoint/bank-info/bank-code) - Fetch list of Nigerian banks
* [Name Enquiry endpoint](/api-reference/endpoint/bank-info/name-enquiry) - Validate bank account details
### Quick Reference
**Get Bank Codes:**
```bash theme={null}
GET /api/v1/bank/bankcode?country=NG
```
**Name Enquiry:**
```bash theme={null}
GET /api/v1/bank/name-enquiry?accountNumber={accountNumber}&bankCode={bankCode}
```
## Error Handling
### Common Errors
| Error | Cause | Solution |
| --------------------------- | ----------------------------------------- | -------------------------------------------------------- |
| Invalid account number | Account doesn't exist or wrong number | Ask user to verify their account number |
| Invalid bank code | Bank code doesn't match any Nigerian bank | Ensure you're using the NIP bank code from the bank list |
| Account verification failed | Temporary issue with bank API | Retry after a short delay |
| Rate limit exceeded | Too many requests | Implement rate limiting in your application |
### Example Error Response
```json theme={null}
{
"status": "error",
"message": "Invalid account number or bank code"
}
```
## Best Practices
1. **Cache Bank List**: Fetch and cache the bank list instead of fetching on every page load
2. **Validate Before Enquiry**: Check account number is exactly 10 digits before calling API
3. **Show Loading State**: Display loading indicator during verification
4. **Handle Errors Gracefully**: Provide clear error messages to users
5. **Confirm Before Proceeding**: Always show retrieved name for user confirmation
6. **Store Validated Data**: Save validated beneficiary details for future use
## Security Considerations
1. **API Key Protection**: Never expose your API key in frontend code
2. **Backend Validation**: Always verify account details on your backend
3. **Rate Limiting**: Implement rate limiting to prevent abuse
4. **Input Validation**: Validate all user inputs before making API calls
## Limitations
* Only available for Nigerian (NGN) bank accounts
* Not available for South African Rand (ZAR) or Kenyan Shilling (KES)
* Requires valid NIP bank code
* Account must be active and valid
## Related Documentation
Complete API reference for the Name Enquiry endpoint
API reference for fetching Nigerian bank codes
Required formats for all supported currencies
Learn how to create quotes with validated details
## Next Steps
* View [beneficiary formats](/api-reference/beneficiary-formats) for all currencies
* Learn how to [create quotes](/api-reference/endpoint/quote/get-quote)
* Explore [quote examples](/api-reference/quote-examples) for different currency pairs
* Set up [webhook notifications](/webhooks/introduction) for payment tracking
# NGN to GHS
Source: https://docs.mavapay.co/guides/ngn-ghs
Complete guide to sending Nigerian Naira to Ghanaian Cedi mobile money accounts using the Mavapay API
## Overview
Mavapay allows you to send Nigerian Naira (NGN) to Ghanaian Cedi (GHS) beneficiaries through MTN Mobile Money. This guide walks you through creating a quote, funding it, and tracking payout completion.
GHS payouts currently support `MTN` beneficiaries only.
## Integration Flow
### Step 1: Create a Quote
Create a quote to lock in the rate and generate a virtual NGN account to fund the transfer.
#### Understanding Payment Currency
The `paymentCurrency` parameter determines what your `amount` means:
* **`paymentCurrency: "NGNKOBO"`** -> amount is in NGN Kobo (you specify how much Naira to send)
* **`paymentCurrency: "GHSPESEWA"`** -> amount is in GHS Pesewa (you specify how much recipient should receive)
#### Option A: Send a Specific NGN Amount
When you want to send exactly NGN 10,000:
```bash theme={null}
curl --request POST \
--url https://api.mavapay.co/api/v1/quote \
--header 'content-type: application/json' \
--header 'x-api-key: ' \
--data '{
"amount": "1000000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "GHSPESEWA",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"customerInternalFee": "0",
"skipVirtualAccount": false,
"beneficiary": {
"identifierType": "MTN",
"identifiers": {
"phoneNumber": "+233999992800",
"accountName": "Mensah"
}
}
}'
```
#### Option B: Recipient Gets a Specific GHS Amount
When the recipient should receive exactly GHS 10.00:
```bash theme={null}
curl --request POST \
--url https://api.mavapay.co/api/v1/quote \
--header 'content-type: application/json' \
--header 'x-api-key: ' \
--data '{
"amount": "1000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "GHSPESEWA",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "GHSPESEWA",
"autopayout": true,
"customerInternalFee": "0",
"skipVirtualAccount": false,
"beneficiary": {
"identifierType": "MTN",
"identifiers": {
"phoneNumber": "+233999992800",
"accountName": "Mensah"
}
}
}'
```
### Step 2: Review Quote Response
A successful response returns the quote details and a virtual NGN account to fund:
```json theme={null}
{
"status": "ok",
"data": {
"id": "18e2c0f8-a81e-46e1-ad9d-83381b79b62b",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "GHSPESEWA",
"amountInSourceCurrency": 1000000,
"amountInTargetCurrency": 1000,
"totalAmountInSourceCurrency": 1000000,
"hash": "69c3dbce8c054100124b86d1",
"expiry": "2026-03-25T13:07:50.483Z",
"ngnBankAccountNumber": "6167623224",
"ngnAccountName": "Mava Digital Solutions Limited",
"bankName": "wema",
"orderId": "38601-7270",
...other fields...
}
}
```
### Step 3: Fund the Virtual NGN Account
Transfer the exact `totalAmountInSourceCurrency` (in Kobo) to the `ngnBankAccountNumber` before quote expiry.
Send the exact amount on the quote. Underpayments and overpayments may delay processing.
### Step 4: Track Payout Status
You'll receive webhook updates as the payment progresses:
1. `payment.received` - NGN funding is confirmed
2. `payment.sent` - GHS payout is sent to the MTN beneficiary
You can also query by transaction hash:
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/transaction?hash=' \
--header 'x-api-key: '
```
## Beneficiary Format (GHS MTN)
Use the following beneficiary structure for NGN to GHS:
```json theme={null}
{
"beneficiary": {
"identifierType": "MTN",
"identifiers": {
"phoneNumber": "+233999992800",
"accountName": "Mensah"
}
}
}
```
| Field | Type | Required | Description |
| ------------------------- | ------ | -------- | ---------------------------------------------------------- |
| `identifierType` | string | Yes | Must be `MTN` |
| `identifiers.phoneNumber` | string | Yes | Beneficiary MTN number in international format (`+233...`) |
| `identifiers.accountName` | string | Yes | Beneficiary account name |
## Important Considerations
### Currency Denominations
All amounts are in the lowest denomination:
* **NGNKOBO**: 100 Kobo = NGN 1
* **GHSPESEWA**: 100 Pesewa = GHS 1
### Quote Expiration
Quotes expire quickly. Always display expiry time and prompt users to fund immediately.
### Autopayout
Set `autopayout: true` to process payout automatically after funding confirmation.
## Related Resources
* [GHS Payout Guide](/payouts/ghs-payout)
* [Create Quote API](/api-reference/endpoint/quote/get-quote)
* [Beneficiary Formats](/guides/beneficiary-formats)
* [Webhook Examples](/webhooks/webhook-examples)
# NGN Withdrawal Workflow
Source: https://docs.mavapay.co/guides/ngn-withdrawal
Learn how to perform NGN withdrawals using the Mavapay API
This guide details the standard workflow for performing an NGN withdrawal to a local bank account using the Mavapay API. To ensure successful processing, you must follow the verification-payout sequence strictly.
***
## **The NGN Withdrawal Workflow**
### **Step 1: Fetch and Cache Bank List**
Before initiating a withdrawal, you need the list of supported Nigerian banks. Each bank has a unique `nipBankCode` required for the next steps.
> **Note:** It is highly recommended to **cache this list** for 24 hours to reduce latency and API calls.
You can use the [Get Bank Code](/api-reference/endpoint/bank-info/bank-code) endpoint to retrieve this list.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/bank/bankcode?country=NG' \
--header 'x-api-key: YOUR_API_KEY'
```
**Response Sample:**
```json theme={null}
{
"status": "ok",
"data": [
{
"bankName": "GTBANK PLC",
"nipBankCode": "000013"
}
]
}
```
***
### **Step 2: Bank Account Verification (Name Enquiry)**
Once a user provides their bank account number and selects a bank name, you must verify the details using the [Name Enquiry](/api-reference/endpoint/bank-info/name-enquiry) endpoint. This step confirms the recipient's identity and provides the exact name required by the clearing system.
```bash theme={null}
# Replace bankCode with the nipBankCode from Step 1
curl --location 'https://api.mavapay.co/api/v1/bank/name-enquiry?accountNumber=0149203789&bankCode=000013' \
--header 'x-api-key: YOUR_API_KEY'
```
**Critical Requirement:**
You must receive a `status: "ok"` and a `200` response before proceeding. Store the `accountName` returned exactly as it appears.
***
### **Step 3: Initiate Withdrawal**
With the verified details, you can now trigger the payout using the [Withdraw Fiat](/api-reference/endpoint/withdraw/withdraw-fiat) endpoint. Use the exact data returned from the Name Enquiry endpoint to avoid validation errors.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/withdraw' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"amount": "100000",
"currency": "NGN",
"beneficiary": {
"bankAccountNumber": "0123456789",
"bankAccountName": "JOHN DOE",
"bankCode": "000013",
"bankName": "GTBANK PLC"
}
}'
```
***
## **Important Implementation Notes**
* **Exact Matching:** The `bankCode`, `bankName`, and `bankAccountName` must match the Name Enquiry response **character-for-character**. A single extra space or a missing period will cause the request to fail.
* **Currency Denomination:** Ensure you verify if the amount is in Kobo or Naira. For this endpoint, `100000` is typically evaluated as **1,000.00 NGN** (Kobo denomination).
* **Instant Settlement:** Withdrawals are processed instantly via NIP (NIBSS Instant Payment).
* **Webhooks:** Mavapay will send a webhook notification to your configured URL once the transaction reaches a final state (`SUCCESS`, `PENDING` or `FAILED`).
### **Error Checklist**
| Error Scenario | Likely Cause |
| ------------------------ | ---------------------------------------------------------------------------------- |
| **Name Mismatch** | The name sent in the withdrawal does not match the bank's record. |
| **Invalid Bank Code** | You used the NGN bank code instead of the specific NIP code. |
| **Insufficient Balance** | Your internal Mavapay NGN wallet does not have enough funds for the payout + fees. |
# NGN to ZAR
Source: https://docs.mavapay.co/guides/ngn-zar
Complete guide to sending Nigerian Naira to South African Rand bank accounts using the Mavapay API
## Overview
Mavapay allows you to send Nigerian Naira (NGN) to South African bank accounts in Rand (ZAR). This guide walks you through fetching supported banks, getting exchange rates, creating quotes, and tracking payouts.
ZAR payouts require `autopayout: true`. Unlike NGN or BTC flows, there is no manual payout option — funds are sent directly to the beneficiary's bank account.
## Integration Flow
### Step 1: Fetch Supported South African Banks
Before initiating a payout, retrieve the list of supported South African banks. Use the country ISO code `ZA`.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/bank/bankcode?country=ZA' \
```
**Response:**
```json theme={null}
{"status":"ok","message":"all bank codes","data":["ABSA","ACCESS BANK","AFRICAN BANK","ALBARAKA BANK","BANK ZERO MUTUAL BANK","BIDVEST BANK","CAPITEC BANK","CAPITEC BUSINESS BANK","DISCOVERY BANK","FINBOND EPE","FINBOND MUTUAL BANK","FNB","INVESTEC BANK LIMITED","NEDBANK","STANDARD BANK","TYMEBANK","UBANK LTD"]}
```
Cache the bank list for 24 hours as this list rarely changes.
### Step 2: Get the Exchange Rate (Optional)
Use the price ticker endpoint to preview the current rate before creating a quote. The `ask` price reflects the rate for sending 1 Rand using NGN.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/price/ticker?pair=zarngn'
```
**Response:**
```json theme={null}
{
"status": "ok",
"data": {
"ask": 84.12,
"bid": 81.69,
"timestamp": 1774509048809,
"meta": {
"pair": "ZARNGN",
"payoutMethods": ["BANKTRANSFER"],
"collectionMethods": ["BANKTRANSFER", "LIGHTNING"],
"fees": {
"ask": { "fee": 0, "minFee": 0, "maxFee": null, "feePercentage": 0 },
"bid": { "fee": 0, "minFee": 0, "maxFee": null, "feePercentage": 0 }
}
}
}
}
```
The rate is returned as the price of **1 Rand to Nigerian Naira**. Use the ticker for indicative pricing only.
### Step 3: Create a Quote
Create a quote to lock in the exchange rate and receive a virtual NGN bank account to fund the transfer. The quote is valid for 10 minutes after which the virtual account number expires.
#### Understanding Payment Currency
The `paymentCurrency` parameter determines what the `amount` field represents:
* **`paymentCurrency: "NGNKOBO"`** → amount is in Naira (you're specifying how much Naira to send)
* **`paymentCurrency: "ZARCENT"`** → amount is in Rand (you're specifying how much the recipient should receive)
#### Option A: Send a Specific NGN Amount
When you want to send exactly ₦5,000:
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote' \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"amount": "500000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"name": "John Doe",
"bankName": "STANDARD BANK",
"bankAccountNumber": "123456789"
}
}'
```
#### Option B: Recipient Gets a Specific ZAR Amount
When the recipient should receive exactly R500:
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote' \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"amount": "50000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "ZARCENT",
"autopayout": true,
"beneficiary": {
"name": "John Doe",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "987654321"
}
}'
```
### Step 4: Review the Quote Response
The response includes a virtual NGN bank account for you to fund the transfer:
```json theme={null}
{
"status": "ok",
"data": {
"id": "18e2c0f8-a81e-46e1-ad9d-83381b79b62b",
"exchangeRate": 1213825,
"usdToTargetCurrencyRate": 16.93315176,
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"transactionFeesInSourceCurrency": 2830,
"transactionFeesInTargetCurrency": 19,
"amountInSourceCurrency": 500000,
"amountInTargetCurrency": 3983,
"paymentMethod": "BANKTRANSFER",
"expiry": "2026-03-25T13:07:50.483Z",
"isValid": true,
"invoice": "",
"hash": "69c3dbce8c054100124b86d1",
"totalAmountInSourceCurrency": 500000,
"customerInternalFee": 0,
"bankName": "wema",
"ngnBankAccountNumber": "6167623224",
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankCode": "000017",
"orderId": "38601-7270"
}
}
```
**Key Fields:**
| Field | Description |
| ----------------------------- | ------------------------------------------------ |
| `ngnBankAccountNumber` | The virtual account to transfer Naira to |
| `ngnBankName` | Bank name for the virtual account |
| `ngnAccountName` | Account name on the virtual account |
| `totalAmountInSourceCurrency` | Exact amount in Kobo to transfer (includes fees) |
| `amountInTargetCurrency` | Amount in Cents the recipient will receive |
| `exchangeRate` | BTC-ZAR exchange rate applied |
| `hash` | Transaction hash for tracking |
| `expiry` | Quote expiration timestamp |
### Step 5: Make the NGN Transfer
Transfer the exact `totalAmountInSourceCurrency` (in Kobo) to the provided `ngnBankAccountNumber`.
You must send the **exact amount** shown in the quote. Partial payments are automatically rejected and refunded.
### Step 6: Track Payment Status
You'll receive webhooks as the transaction progresses:
1. **`payment.received`** — Naira transfer confirmed
2. **`payment.sent`** — Rand disbursed to the beneficiary's bank account
```json theme={null}
// payment.received
{
"event": "payment.received",
"data": {
"id": "18e2c0f8-a81e-46e1-ad9d-83381b79b62b",
"hash": "69c3dbce8c054100124b86d1",
"currency": "ZAR",
"type": "DEPOSIT",
"status": "SUCCESS",
...
}
}
```
```json theme={null}
// payment.sent
{
"event": "payment.sent",
"data": {
"id": "18e2c0f8-a81e-46e1-ad9d-83381b79b62b",
"hash": "69c3dbce8c054100124b86d1",
"currency": "ZAR",
"type": "WITHDRAWAL",
"status": "SUCCESS",
...
}
}
```
You can also manually check the transaction status using the `hash`:
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/transaction?hash=' \
--header 'x-api-key: '
```
## Beneficiary Format
The ZAR beneficiary object requires three fields:
```json theme={null}
{
"beneficiary": {
"name": "John Doe",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}
```
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------- |
| `name` | string | Yes | Beneficiary's full name |
| `bankName` | string | Yes | South African bank name (use exact name from bank list) |
| `bankAccountNumber` | string | Yes | South African bank account number |
Bank account validation is not currently available for ZAR. Ensure account details are correct before creating a quote.
## Testing in Staging
Test your integration without real funds using the staging environment:
```
https://staging.api.mavapay.co/api/v1
```
Use the [Simulation endpoint](/guides/simulation) to test payment flows:
```bash theme={null}
# Step 1: Create a quote in staging
curl --location 'https://staging.api.mavapay.co/api/v1/quote' \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"amount": "300000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"name": "test merchant 1",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}'
# Step 2: Simulate the NGN payment
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
--header 'Content-Type: application/json' \
--header 'x-api-key: ' \
--data '{
"currency": "NGN",
"quoteId": "",
"amount": 300000
}'
```
## Important Considerations
### Currency Denominations
All amounts are in the lowest denomination:
* **NGNKOBO**: 100 Kobo = ₦1 (so `500000` = ₦5,000)
* **ZARCENT**: 100 Cents = R1 (so `50000` = R500)
### Quote Expiration
Quotes expire after approximately **10 minutes**. Display a countdown timer and prompt users to transfer quickly.
### Exchange Rate Volatility
Rates update frequently. Create a fresh quote each time — don't cache quotes for long periods.
### Autopayout Requirement
ZAR payouts **must** have `autopayout: true`. Unlike NGN or BTC flows, there is no manual wallet option for Rand.
## Related Resources
* [Create Quote API](/api-reference/endpoint/quote/get-quote) - Interactive API documentation
* [Beneficiary Formats](/guides/beneficiary-formats) - Required formats for all currencies
* [Webhook Examples](/webhooks/webhook-examples) - Complete webhook payloads
* [Payment Simulation](/guides/simulation) - Testing in staging
* [Quote Examples](/guides/quote-examples) - Examples for all currency pairs
## FAQ
**Q: What is the minimum amount for NGN to ZAR?**
A: Minimum amount is 50 Rands.
**Q: How long does the ZAR payout take?**
A: Payouts are typically processed within minutes of the NGN transfer being confirmed.
**Q: Can I cancel a quote after creating it?**
A: Quotes expire automatically. If no transfer is made, the quote simply lapses.
**Q: What happens if I send the wrong amount?**
A: Partial or incorrect payments are not automatically processed. Contact support with the `hash` for manual handling.
# Prefunded Quote (Internal Wallet)
Source: https://docs.mavapay.co/guides/prefunded-internal-wallet
Learn how to create a quote funded from your internal wallet and accept it for payout
This guide covers how to create a [prefunded quote](/api-reference/endpoint/quote/get-quote) and settle it using your internal wallet balance. A prefunded quote is identical to a standard quote — the only difference is the `sourceWallet` field, which tells Mavapay to deduct the source amount directly from your internal wallet instead of expecting an external bank transfer or Lightning payment.
***
## **Prefunded Quote Flow**
Performing a prefunded payout is a two-step process: **Requesting a Quote** and **Accepting the Quote from your Wallet**.
### **Step 1: Create a Prefunded Quote**
The [quote endpoint](/api-reference/endpoint/quote/get-quote) calculates the exchange rate and fees. The request is the same as a standard quote, with one addition: the `sourceWallet` field.
**The `sourceWallet` field** is the only difference between a prefunded quote and a regular quote. It specifies which internal wallet to debit for the source amount. The value must correspond to the `sourceCurrency` in the quote body (e.g. `USDTCENT` → `USDT`, `BTCSAT` → `BTC`, `NGNKOBO` → `NGN`).
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/quote' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"amount": "1000",
"sourceCurrency": "USDTCENT",
"targetCurrency": "NGNKOBO",
"paymentMethod": "ONCHAIN",
"paymentCurrency": "USDTCENT",
"sourceWallet": "USDT",
"autopayout": true,
"beneficiary": {
"bankAccountNumber": "7012345678",
"bankAccountName": "Example Customer",
"bankCode": "100004",
"bankName": "OPAY"
}
}'
```
### **Step 2: The Quote Response**
Mavapay returns a quote object. Key field to note:
* **`id`** — Use this when accepting the quote from your wallet.
> **Note:** Quotes are typically valid for **5–10 minutes**. You must accept before the `expiry` timestamp.
```json theme={null}
{
"status": "ok",
"data": {
"id": "b01af569-7ad3-4803-ad3c-4aa368ee98bf",
"exchangeRate": 109495302.39059484,
"usdToTargetCurrencyRate": 1374.033506,
"sourceCurrency": "USDTCENT",
"targetCurrency": "NGNKOBO",
"transactionFeesInSourceCurrency": 8,
"transactionFeesInTargetCurrency": 10305,
"amountInSourceCurrency": 1000,
"amountInTargetCurrency": 1363041,
"paymentMethod": "ONCHAIN",
"expiry": "2026-08-27T11:16:41.564Z",
"isValid": true,
"invoice": "",
"hash": "",
"totalAmountInSourceCurrency": 1000,
"customerInternalFee": 0,
"invoiceRequired": false,
"onchainAddress": "MPY_USDTCENT_1787828801564",
"network": "trc20",
"memo": "MAVAPAY_INTERNAL",
"orderId": "260827-GDTV6-28K3N"
}
}
```
### **Step 3: Accept and Settle from Wallet**
To finalize the payout, call the [accept endpoint](/api-reference/endpoint/wallet/accept-quote). This will immediately deduct the `totalAmountInSourceCurrency` from your internal wallet and process the payout to the beneficiary.
* **Endpoint:** `POST .../wallet/payout/{quoteId}/accept`
* **Payload:** The `sourceWallet` value must match the one used in the original quote.
```bash theme={null}
curl --location 'https://api.mavapay.co/api/v1/wallet/payout/b01af569-7ad3-4803-ad3c-4aa368ee98bf/accept' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"sourceWallet": "USDT"
}'
```
**200 Response:**
```json theme={null}
{
"status": "ok",
"message": "quote is being processed"
}
```
**400 Response (Insufficient Balance):**
```json theme={null}
{
"status": "error",
"message": "Error processing internal transfer: Insufficient balance in USDT wallet"
}
```
***
## **Key Considerations**
* **Sufficient Balance:** Ensure your internal wallet has enough funds to cover the `totalAmountInSourceCurrency` (which includes fees).
* **Rate Volatility:** Because exchange rates change rapidly, the quote `id` is short-lived. If it expires, you must generate a new quote.
* **Denominations:** All amounts are in the lowest denomination (cents for USDTCENT, kobo for NGNKOBO, satoshis for BTCSAT).
## **Related Resources**
* [Create Quote API](/api-reference/endpoint/quote/get-quote) — Full quote endpoint reference
* [Accept Quote API](/api-reference/endpoint/wallet/accept-quote) — Wallet accept endpoint reference
* [Beneficiary Formats](/guides/beneficiary-formats) — Required beneficiary formats for each currency
* [Internal Currency Swap](/guides/internal-currency-swap) — Similar flow for swapping between internal wallets
# Quote Examples
Source: https://docs.mavapay.co/guides/quote-examples
Comprehensive examples for creating quotes across all supported currency pairs
# Quote Request & Response Examples
This guide provides complete request and response examples for all supported currency pairs on Mavapay.
## Supported Currency Pairs
Mavapay supports the following currency conversions:
* BTCSAT => NGNKOBO (Bitcoin to Nigerian Naira)
* BTCSAT => ZARCENT (Bitcoin to South African Rand)
* BTCSAT => KESCENT (Bitcoin to Kenyan Shilling)
* NGNKOBO => BTCSAT (Nigerian Naira to Bitcoin)
* NGNKOBO => ZARCENT (Nigerian Naira to South African Rand)
* NGNKOBO => KESCENT (Nigerian Naira to Kenyan Shilling)
**Get Price endpoint possible values:** `NGN`, `KES`, `ZAR`
## Bitcoin to Kenyan Shilling (BTCSAT => KESCENT)
### Request
```json theme={null}
{
"amount": "100000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "John Doe",
"network": "mpesa"
}
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "1577613a-6dc6-4af9-b0b3-e14d356c5a72",
"exchangeRate": 14720643,
"usdToTargetCurrencyRate": 128.99896800825593,
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"transactionFeesInSourceCurrency": 498,
"transactionFeesInTargetCurrency": 7340,
"amountInSourceCurrency": 100000,
"amountInTargetCurrency": 1460784,
"paymentMethod": "LIGHTNING",
"expiry": "2025-09-30T23:18:03.000Z",
"isValid": true,
"invoice": "lntbs1m1p5dccllpp590cxxjtkhxql7xl9frxuc56nq5svf28vrl5w073uzvt0ytjfuedsdz5296...",
"hash": "2bf0634976b981ff1be548cdcc53530520c4a8ec1fe8e7fa3c1316f22e49e65b",
"totalAmountInSourceCurrency": 100000,
"customerInternalFee": 0,
"orderId": "63493-3037"
}
}
```
## Nigerian Naira to Kenyan Shilling (NGNKOBO => KESCENT)
### Request
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "KESCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "John Doe",
"network": "airtel"
}
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "ab60815e-b939-4634-bb84-9b80499f026f",
"exchangeRate": 15191446,
"usdToTargetCurrencyRate": 128.99896800825593,
"sourceCurrency": "NGNKOBO",
"targetCurrency": "KESCENT",
"transactionFeesInSourceCurrency": 5860,
"transactionFeesInTargetCurrency": 440,
"amountInSourceCurrency": 1000000,
"amountInTargetCurrency": 87590,
"paymentMethod": "BANKTRANSFER",
"expiry": "2025-10-01T20:29:56.497Z",
"isValid": true,
"invoice": "",
"hash": "68dd8ced9145820012b30903",
"totalAmountInSourceCurrency": 1000000,
"customerInternalFee": 0,
"bankName": "WEMA BANK",
"ngnBankAccountNumber": "7824655568",
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankCode": "000027",
"orderId": "52209-7505"
}
}
```
When using BANKTRANSFER as payment method, the response includes bank details where you should send the NGN payment.
## Bitcoin to Nigerian Naira (BTCSAT => NGNKOBO)
### Request
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"bankAccountName": "olaolu olajide",
"bankCode": "000013",
"bankName": "GTBANK PLC",
"bankAccountNumber": "0087241555"
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "e3cdeb26-5375-4758-adb0-957f0de97c4d",
"exchangeRate": 168375961.2972606,
"usdToTargetCurrencyRate": 1434.964752,
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"transactionFeesInSourceCurrency": 59,
"transactionFeesInTargetCurrency": 10000,
"amountInSourceCurrency": 6016,
"amountInTargetCurrency": 1000000,
"paymentMethod": "LIGHTNING",
"expiry": "2025-10-01T20:27:57.000Z",
"isValid": true,
"invoice": "lntbs60160n1p5dmrdppp5vadgr37wqyn3vudwkhf3qqyq0tk7nkc7k2dw4pg0zk2fd7f28r3s...",
"hash": "675a81c7ce01271671aeb5d31000807aede9db1eb29aea850f159496f92a38e3",
"totalAmountInSourceCurrency": 6016,
"customerInternalFee": 0,
"orderId": "56188-2561"
}
}
```
## Bitcoin to South African Rand (BTCSAT => ZARCENT)
### Request
```json theme={null}
{
"amount": "1000000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "ZARCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "ZARCENT",
"autopayout": true,
"beneficiary": {
"name": "test merchant 1",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "f53d1e18-acfb-445e-a78f-6f781653f7da",
"exchangeRate": 2026652.625,
"usdToTargetCurrencyRate": 17.22934675,
"sourceCurrency": "BTCSAT",
"targetCurrency": "ZARCENT",
"transactionFeesInSourceCurrency": 2467,
"transactionFeesInTargetCurrency": 5000,
"amountInSourceCurrency": 500408,
"amountInTargetCurrency": 1000000,
"paymentMethod": "LIGHTNING",
"expiry": "2025-10-01T20:30:33.000Z",
"isValid": true,
"invoice": "lntbs5004080n1p5dmr3app52h5ag3n3p5x74utqxgg3wug6a2ladey8kfmc325kjypcdfh47...",
"hash": "55e9d446710d0deaf160321117711aeabfd6e487b27788aa96910386a6f5f36d",
"totalAmountInSourceCurrency": 500408,
"customerInternalFee": 0,
"orderId": "62602-9933"
}
}
```
## Nigerian Naira to South African Rand (NGNKOBO => ZARCENT)
### Request
```json theme={null}
{
"amount": "300000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"name": "test merchant 1",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1325558779"
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "9e6f8a73-79d3-44d3-b8f4-000fdc8a9199",
"exchangeRate": 2028008,
"usdToTargetCurrencyRate": 17.22934675,
"sourceCurrency": "NGNKOBO",
"targetCurrency": "ZARCENT",
"transactionFeesInSourceCurrency": 128850,
"transactionFeesInTargetCurrency": 1513,
"amountInSourceCurrency": 300000,
"amountInTargetCurrency": 301238,
"paymentMethod": "BANKTRANSFER",
"expiry": "2025-10-01T20:39:27.550Z",
"isValid": true,
"bankName": "WEMA BANK",
"ngnBankAccountNumber": "7824829602",
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankCode": "000027",
"orderId": "59651-5544"
}
}
```
## Nigerian Naira to Bitcoin (NGNKOBO => BTCSAT)
### Request
```json theme={null}
{
"amount": "2000",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "BTCSAT",
"autopayout": true,
"beneficiary": {
"lnInvoice": "lntbs20u1p5dmy87pp5l2pm..."
}
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": {
"id": "2ca718ea-4a41-4e8a-aa63-36d27e337c21",
"exchangeRate": 1,
"usdToTargetCurrencyRate": 0.000008515171041180565,
"sourceCurrency": "NGNKOBO",
"targetCurrency": "BTCSAT",
"transactionFeesInSourceCurrency": 2927,
"transactionFeesInTargetCurrency": 10,
"amountInSourceCurrency": 346900,
"amountInTargetCurrency": 2000,
"paymentMethod": "BANKTRANSFER",
"expiry": "2025-10-01T20:48:03.778Z",
"isValid": true,
"hash": "68dd912c9145820012b30963",
"totalAmountInSourceCurrency": 346900,
"customerInternalFee": 0,
"bankName": "WEMA BANK",
"ngnBankAccountNumber": "7824182598",
"ngnAccountName": "Mava Digital Solutions Limited",
"ngnBankCode": "000027",
"orderId": "21997-7910"
}
}
```
## Understanding Quote Fields
| Field | Description |
| --------------------------------- | ----------------------------------------------------------- |
| `exchangeRate` | The exchange rate between source and target currencies |
| `usdToTargetCurrencyRate` | USD to target currency exchange rate |
| `transactionFeesInSourceCurrency` | Fees in the source currency's lowest denomination |
| `transactionFeesInTargetCurrency` | Fees in the target currency's lowest denomination |
| `amountInSourceCurrency` | Amount to be paid in source currency |
| `amountInTargetCurrency` | Amount recipient receives in target currency |
| `totalAmountInSourceCurrency` | Total amount including fees |
| `customerInternalFee` | Custom fee you can set for your customers |
| `invoice` | Lightning invoice to pay (when payment method is LIGHTNING) |
| `hash` | Payment hash for tracking the transaction |
| `orderId` | Unique order identifier |
| `expiry` | Quote expiration timestamp |
All amounts are in the lowest denomination of the currency (kobo for NGN, cents for KES/ZAR, satoshis for BTC).
## Next Steps
* Learn about [beneficiary formats](/api-reference/beneficiary-formats) for different currencies
* Explore [webhook integration](/webhooks/introduction) for payment notifications
* Test your integration in our [staging environment](https://staging.api.mavapay.co/api/v1)
# Payment Simulation
Source: https://docs.mavapay.co/guides/simulation
Test quote payments in staging environment using the simulation endpoint
# Payment Simulation
The simulation endpoint allows you to test payment flows in the staging environment without making actual payments. This is useful for testing your integration and webhook handlers.
The simulation endpoint is only available in the **staging environment**: `https://staging.api.mavapay.co/api/v1`
## Endpoint
```
POST /api/v1/simulation/pay-in
```
## How It Works
1. Create a quote using the [Create Quote endpoint](/api-reference/endpoint/quote/get-quote)
2. Use the `quoteId` (or `orderId` if you don't have the quote) from the response
3. Call the simulation endpoint to simulate payment
4. Your webhook will receive `payment.received` event
5. If autopayout is enabled, you'll also receive `payment.sent` event
## Nigerian Naira (NGN) Simulation
### Request
```json theme={null}
{
"currency": "NGN",
"quoteId": "c523383d-d44c-4d2e-be48-b64bcaf7b403",
"amount": 604300
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": "PayIn simulation successful"
}
```
### Parameters
| Parameter | Type | Required | Description |
| ---------- | ------------- | -------- | ------------------------------------------------- |
| `currency` | string | Yes | Must be `"NGN"` |
| `quoteId` | string (UUID) | No\* | The quote ID from your created quote |
| `orderId` | string (UUID) | No\* | The order ID (use if you don't have the quote ID) |
| `amount` | number | Yes | Amount in kobo (must match quote amount) |
\*Either `quoteId` or `orderId` is required.
## Bitcoin (BTC) Simulation
### Request
```json theme={null}
{
"currency": "BTC",
"quoteId": "a523383d-d44c-4d2e-be48-b64bcaf7b404"
}
```
### Response
```json theme={null}
{
"status": "ok",
"data": "PayIn simulation successful"
}
```
### Parameters
| Parameter | Type | Required | Description |
| ---------- | ------------- | -------- | ------------------------------------------------- |
| `currency` | string | Yes | Must be `"BTC"` |
| `quoteId` | string (UUID) | No\* | The quote ID from your created quote |
| `orderId` | string (UUID) | No\* | The order ID (use if you don't have the quote ID) |
\*Either `quoteId` or `orderId` is required.
For BTC simulations, the `amount` parameter is not required as it's determined by the Lightning invoice.
## Payment Link Simulation
For Payment Links (including on-chain Bitcoin purchases), you need to first retrieve the order ID.
### Step 1: Get the Order ID
Retrieve the order ID associated with your payment link:
```bash theme={null}
curl -X GET "https://staging.api.mavapay.co/api/v1/paymentlink/orders?id={paymentRef}" \
-H "X-API-KEY: your_staging_api_key"
```
### Step 2: Simulate Payment
Use the order ID to simulate the bank transfer:
```bash theme={null}
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_staging_api_key" \
-d '{
"amount": 800000,
"currency": "NGN",
"orderId": "11110-0001"
}'
```
For Payment Link simulations, use `orderId` instead of `quoteId`. The amount should match the payment link amount in kobo.
***
## Complete Testing Flow
### Step 1: Create a Quote
```bash theme={null}
curl -X POST https://staging.api.mavapay.co/api/v1/quote \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_staging_api_key" \
-d
'{
"amount": "604300",
"sourceCurrency": "NGNKOBO",
"targetCurrency": "KESCENT",
"paymentMethod": "BANKTRANSFER",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "John Doe",
"network": "mpesa"
}
}
}'
```
### Step 2: Simulate Payment
```bash theme={null}
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_staging_api_key" \
-d '{
"currency": "NGN",
"quoteId": "c523383d-d44c-4d2e-be48-b64bcaf7b403",
"amount": 604300
}'
```
### Step 3: Receive Webhook
Your registered webhook endpoint will receive a `payment.received` event:
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "0b234e32-d5ba-4c2a-8f71-0b820a477f01",
"amount": 604300,
"currency": "NGN",
"type": "DEPOSIT",
"status": "SUCCESS",
...
}
}
```
If autopayout is enabled, you'll also receive a `payment.sent` event shortly after.
## Testing Different Scenarios
### Test Successful Payment
Use the simulation endpoint as shown above to test successful payment flows.
### Using Order ID Instead of Quote ID
If you don't have access to the quote ID, you can use the order ID instead:
```bash theme={null}
curl -X POST https://staging.api.mavapay.co/api/v1/simulation/pay-in \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_staging_api_key" \
-d '{
"currency": "NGN",
"orderId": "5466-3532",
"amount": 604300
}'
```
### Test Different Currency Pairs
Create quotes for different currency pairs and simulate payments:
```javascript theme={null}
// BTCSAT => KESCENT
const btcToKes = await createQuote({
amount: "100000",
sourceCurrency: "BTCSAT",
targetCurrency: "KESCENT",
...
});
await simulatePayment({
currency: "BTC",
quoteId: btcToKes.data.id
});
// NGNKOBO => ZARCENT
const ngnToZar = await createQuote({
amount: "300000",
sourceCurrency: "NGNKOBO",
targetCurrency: "ZARCENT",
...
});
await simulatePayment({
currency: "NGN",
quoteId: ngnToZar.data.id,
amount: 300000
});
```
### Test Webhook Handling
1. Set up a webhook endpoint using [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com)
2. Register your webhook in staging
3. Create a quote and simulate payment
4. Verify your webhook receives and processes the events correctly
## Common Errors
| Error | Description | Solution |
| -------------------- | ----------------------------------------------- | --------------------------------------------- |
| `Invalid quote ID` | Quote doesn't exist or has expired | Create a new quote and use the fresh quote ID |
| `Amount mismatch` | Simulation amount doesn't match quote amount | Use the exact amount from the quote response |
| `Quote already paid` | Quote has already been paid (simulated or real) | Create a new quote for testing |
| `Invalid currency` | Currency doesn't match quote parameters | Use the correct currency from your quote |
## Best Practices
1. **Fresh Quotes**: Create new quotes for each test to avoid "already paid" errors
2. **Webhook Testing**: Test both `payment.received` and `payment.sent` events
3. **Error Handling**: Test how your system handles different payment statuses
4. **Idempotency**: Verify your webhook handler processes duplicate events correctly
5. **Timing**: Test scenarios where webhooks arrive out of order
## Integration Checklist
* [ ] Create quotes successfully
* [ ] Simulate NGN payments
* [ ] Simulate BTC payments
* [ ] Receive `payment.received` webhooks
* [ ] Receive `payment.sent` webhooks
* [ ] Handle webhook signature verification
* [ ] Implement idempotent webhook processing
* [ ] Test error scenarios
* [ ] Verify beneficiary payouts work correctly
## Next Steps
* Learn about [webhook integration](/webhooks/introduction)
* View [quote examples](/api-reference/quote-examples) for all currency pairs
* Explore [beneficiary formats](/api-reference/beneficiary-formats)
* Test in production with small amounts first
Always thoroughly test your integration in staging before moving to production!
# Introduction
Source: https://docs.mavapay.co/introduction
Welcome to the Mavapay Developer Documentation where you’ll learn how to build amazing payment experiences with the Mavapay API. Regardless of whether you are a developer or a business, a startup or enterprise, Mavapay API is for you. With Mavapay, you can accept payments from customers in Bitcoin, USDT, or local currency, verify customers, and make payouts to your customers mobile money or bank account all in one place.
## Get started
[Create an account](/authentication) to start building with Mavapay.
Set up your account and start building with the Mavapay API
Explore the Mavapay API reference to see what's possible
## Quick Start Guides
If you're ready to dive in, check out our quick start guides to get up and running quickly.
Accept fiat payments and deliver Bitcoin via Lightning Network
Accept fiat payments and deliver Bitcoin to on-chain addresses
Verify your customers bank account details
Make near instant payouts to your customers
Deposit and withdraw USDT on Tron, Ethereum, Plasma, Solana, and Polygon
## Guides
Detailed guides and reference documentation to help you integrate Mavapay.
Request and response examples for all supported currency pairs
Required beneficiary object formats for each supported currency
Validate Nigerian bank account details before creating quotes
Test payment flows in staging without real funds
# Ghanaian Cedi (GHS) Payouts
Source: https://docs.mavapay.co/payouts/ghs-payout
Learn how to integrate Mavapay payouts for Ghanaian Cedi (GHS) with MTN Mobile Money
# Ghanaian Cedi (GHS) Payouts
This guide explains how to integrate Mavapay payouts for recipients in Ghana. At the moment, GHS payouts support **MTN Mobile Money only**.
## Overview
The GHS payout flow is similar to KES pay-to-phone:
1. Create a quote with GHS payout details and MTN beneficiary information
2. Pay the generated lightning invoice
3. Receive webhook notifications for payout status
## Prerequisites
Before you begin, ensure you have:
1. Created a Mavapay account
2. Obtained your API key
3. Set up your webhook endpoint
4. Verified your business account
## Authentication
All API requests must include your API key in the header:
```bash theme={null}
X-API-KEY: your_api_key_here
```
## MTN Mobile Money Quote Request
Use the `quote` endpoint with `identifierType` set to `MTN`:
```bash theme={null}
curl --request POST \
--url https://api.mavapay.co/api/v1/quote \
--header 'content-type: application/json' \
--header 'x-api-key: api-key' \
--data '
{
"amount": "1000",
"sourceCurrency": "BTCSAT",
"targetCurrency": "GHSPESEWA",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "GHSPESEWA",
"autopayout": true,
"customerInternalFee": "0",
"skipVirtualAccount": false,
"beneficiary": {
"identifierType": "MTN",
"identifiers": {
"phoneNumber": "+233999992800",
"accountName": "Mensah"
}
}
}'
```
## Field Notes
* `targetCurrency` and `paymentCurrency` should be `GHSPESEWA`
* `identifierType` must be `MTN` for GHS payouts
* `phoneNumber` should be in international format (for example, `+233...`)
* Set `autopayout` to `true` to trigger payout immediately after invoice settlement
## Webhook Events
For GHS payouts, you will receive:
1. `payment.received` - When we receive the lightning payment
2. `payment.sent` - When funds are successfully sent to the MTN beneficiary
For full event payload examples, see [webhook examples](/webhooks/webhook-examples).
## Support
If you need help with integration:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# Introduction
Source: https://docs.mavapay.co/payouts/introduction
Learn how to integrate Mavapay payouts for supported currencies
# Payouts
Mavapay allows you to send money to bank accounts and mobile money accounts in supported countries using Bitcoin Lightning Network. We currently support payouts to:
1. [Nigerian Naira (NGN)](/payouts/ngn-payout)
2. [South African Rand (ZAR)](/payouts/zar-payout)
3. [Kenyan Shilling (KES)](/payouts/kes-payout)
4. [Ghanaian Cedi (GHS)](/payouts/ghs-payout)
## Overview
The payout process generally involves:
1. Creating a quote for the amount you want to pay out
2. Paying the generated lightning invoice
3. Receiving webhook notifications for payment status
## Currency-Specific Requirements
### Nigerian Naira (NGN)
* Amounts are denominated in Kobo (1 NGN = 100 Kobo)
* Minimum payout amount: 2000 NGN
* Requires bank account validation before payout
* Supports customer internal fees
* [View NGN Payout Documentation](/payouts/ngn-payout)
### South African Rand (ZAR)
* Amounts are denominated in cents (1 ZAR = 100 cents)
* No bank account validation required
* Customer internal fees currently disabled
* [View ZAR Payout Documentation](/payouts/zar-payout)
### Kenyan Shilling (KES)
* Amounts are denominated in cents (1 KES = 100 cents)
* Pay-to-phone supports M-Pesa and Airtel Mobile Money (requires `network` field)
* Supports three payment modes:
1. Mobile Money (pay to phone number — M-Pesa or Airtel)
2. Pay to Bill (M-Pesa business payments)
3. Pay to Account/Till (M-Pesa)
* [View KES Payout Documentation](/payouts/kes-payout)
### Ghanaian Cedi (GHS)
* Amounts are denominated in pesewa (1 GHS = 100 pesewa)
* MTN Mobile Money is currently the only supported payout rail
* Beneficiary identifier type must be `MTN`
* [View GHS Payout Documentation](/payouts/ghs-payout)
## Webhook Events
For all payouts, you'll receive two webhook events:
1. `payment.received` - When we receive the lightning payment
2. `payment.sent` - When the money is sent to the recipient
For detailed webhook implementation, see our [webhook documentation](/webhooks/markdown).
## Testing
Use our staging environment for testing:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
**Note**: All test environment invoices are signet invoices.
## Support
If you need help with integration:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# Kenyan Shilling (KES) Payouts
Source: https://docs.mavapay.co/payouts/kes-payout
Learn how to integrate Mavapay payouts for Kenyan Shilling (KES)
# Kenyan Shilling (KES) Payouts
This guide explains how to integrate Mavapay's payout functionality for sending money to Kenyan recipients through mobile money and M-Pesa payment modes.
## Overview
The KES payout process involves the following steps:
1. Choose the appropriate payment mode (mobile money or M-Pesa paybill/till)
2. Create a quote with the required beneficiary details
3. Pay the generated lightning invoice
4. Receive webhook notifications for payment status
**Note**: KES pay-to-phone payouts support **M-Pesa** and **Airtel Mobile Money**. Pay to Bill and Pay to Till are processed through M-Pesa.
## Prerequisites
Before you begin, ensure you have:
1. Created a Mavapay account
2. Obtained your API key
3. Set up your webhook endpoint
4. Verified your business account
## Authentication
All API requests must include your API key in the header:
```bash theme={null}
X-API-KEY: your_api_key_here
```
**Important Notes:**
* All amounts are in cents (1 KES = 100 cents)
* The minimum amount for KES payouts is \$2
* Phone numbers must be in international format (e.g., +254...)
* Pay-to-phone quotes require a `network` field: `"mpesa"` or `"airtel"`
* Account numbers for paybill payments can be text (usually business name or identifier)
* Pay to Bill and Pay to Till are processed through M-Pesa's payment infrastructure
## Payment Modes
KES payouts support the following modes:
### 1. Pay to Phone (Mobile Money)
Send money directly to a recipient's M-Pesa or Airtel Mobile Money account. You must specify the target network in `beneficiary.identifiers.network`.
Starting **July 12, 2026**, the `network` field is **required** for all pay-to-phone KES quotes. Requests missing this field will return a validation error.
```bash theme={null}
curl --request POST \
--url https://staging.api.mavapay.co/api/v1/quote \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: your_api_key_here' \
--data '{
"amount": 500000,
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "KESCENT",
"autopayout": true,
"beneficiary": {
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980788",
"accountName": "John Doe",
"network": "mpesa"
}
}
}'
```
### 2. Pay to Bill (M-Pesa Paybill)
Pay to a business using their M-Pesa paybill number:
```bash theme={null}
curl --request POST \
--url https://staging.api.mavapay.co/api/v1/quote \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: your_api_key_here' \
--data '{
"amount": 500000,
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "KESCENT",
"autopayout": true,
"beneficiary": {
"identifierType": "paytobill",
"identifiers": {
"paybillNumber": "4141031",
"accountNumber": "business_name"
}
}
}'
```
### 3. Pay to Account/Till (M-Pesa Till)
Pay to a business M-Pesa till number:
```bash theme={null}
curl --request POST \
--url https://staging.api.mavapay.co/api/v1/quote \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: your_api_key_here' \
--data '{
"amount": 500000,
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "KESCENT",
"autopayout": true,
"beneficiary": {
"identifierType": "paytotill",
"identifiers": {
"accountNumber": "till_number"
}
}
}'
```
## Understanding the Quote Response
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"id": "d1614a76-9c7f-4111-98a7-43199f307e5b",
"exchangeRate": 4874805.75,
"usdToTargetCurrencyRate": 145.977,
"sourceCurrency": "BTCSAT",
"targetCurrency": "KESCENT",
"transactionFeesInSourceCurrency": 13.23,
"transactionFeesInTargetCurrency": "3750.00",
"amountInSourceCurrency": 1499,
"amountInTargetCurrency": "50000.00",
"paymentMethod": "LIGHTNING",
"expiry": "2021-07-01T12:00:00Z",
"isValid": true,
"invoice": "lntbs36020n1pnehfzcpp...",
"hash": "189727ef2e0f35921af7858b96e27cbad960b0ec2bb2a4ab73b6a231f9ce8727",
"totalAmountInSourceCurrency": 1499,
"customerInternalFee": 0
}
}
```
Key fields:
* `usdToTargetCurrencyRate`: Price of 1 USD in KES
* `exchangeRate`: Price of 1 BTC in KES
* `amountInSourceCurrency`: Total amount in SATS to be paid (including fees)
* `amountInTargetCurrency`: Total amount in cents to be sent to recipient
* `transactionFeesInSourceCurrency`: Fees in SATS
* `transactionFeesInTargetCurrency`: Fees in cents
* `expiry`: Invoice validity (5 minutes)
## Webhook Events
You'll receive two webhook events:
1. `payment.received` - When the lightning payment is received
2. `payment.sent` - When the money is sent to the recipient
### payment.received Event
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "tx_123456789",
"amount": 500000,
"currency": "KES",
"status": "SUCCESS",
"kesTransactionMetadata": {
"hash": "189727ef2e0f35921af7858b96e27cbad960b0ec2bb2a4ab73b6a231f9ce8727",
"reference": "MP123456789"
}
}
}
```
### payment.sent Event
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "tx_123456789",
"amount": 500000,
"currency": "KES",
"status": "SUCCESS",
"kesTransactionMetadata": {
"identifierType": "paytophone",
"phoneNumber": "+254796980788",
"network": "mpesa",
"reference": "MP123456789"
}
}
}
```
## Checking Transaction Status
You can query transactions using the payment hash, orderId, or transaction ID:
```bash theme={null}
# Using hash
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?hash=08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba' \
--header 'x-api-key: your_api_key_here'
# Using transaction ID
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?id=7bac35f1-2efc-448b-8a71-0d071da43315' \
--header 'x-api-key: your_api_key_here'
```
## Getting Price Rates
To get current KES rates:
```bash theme={null}
curl --location 'https://staging.api.mavapay.co/api/v1/price?currency=KES'
```
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"currency": "KES",
"timestamp": 1738258609,
"btcPriceInUnitCurrency": 4874805.75,
"unitPricePerSat": {
"amount": 0.04874805,
"currencyUnit": "KESSAT"
},
"unitPricePerUsd": {
"amount": 145.977,
"currencyUnit": "KESUSD"
}
}
}
```
## Testing
Use our staging environment for testing:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
Test details:
* Phone Number: `+254790000000`
* Paybill Number: `4141031`
* Account Number: `test_account`
* Till Number: `000000`
**Note**: All test environment invoices are signet invoices.
## Support
If you need help with integration:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# Nigerian Naira (NGN) Payouts
Source: https://docs.mavapay.co/payouts/ngn-payout
This guide explains how to integrate Mavapay's payout functionality for sending money to Nigerian bank accounts.
## Overview
The NGN payout process involves the following steps:
1. Get the list of supported Nigerian banks
2. Validate the recipient's bank account details
3. Create a quote for the amount you want to pay out
4. Pay the generated lightning invoice
5. Receive webhook notifications for payment status
## Prerequisites
Before you begin, ensure you have:
1. Created a Mavapay account
2. Obtained your API key
3. Set up your webhook endpoint
4. Verified your business account
## Authentication
All API requests must include your API key in the header:
```bash theme={null}
X-API-KEY: your_api_key_here
```
## Getting Bank List
First, [retrieve the list of supported Nigerian banks](/api-reference/endpoint/bank-info/bank-code):
```bash theme={null}
curl --location 'https://staging.api.mavapay.co/api/v1/bank/bankcode?country=NG' \
--header 'x-api-key: your_api_key_here'
```
Sample response:
```json theme={null}
{
"status": "ok",
"message": "all bank codes retrieved successfully",
"data": [
{
"bankName": "KUDA MICROFINANCE BANK",
"nipBankCode": "090267"
}
]
}
```
## Validating Bank Account
Before creating a quote, [validate the recipient's bank account details](/api-reference/endpoint/bank-info/name-enquiry):
```bash theme={null}
curl --request GET \
--url 'https://staging.api.mavapay.co/api/v1/bank/name-enquiry?accountNumber=0123456789&bankCode=090267' \
--header 'X-API-KEY: your_api_key_here'
```
Sample response:
```json theme={null}
{
"status": "ok",
"message": "Bank account name verified successfully",
"data": {
"accountName": "Oladeji Olaolu",
"accountNumber": "0123456789",
"kycLevel": "3",
"nameInquiryReference": "123456789",
"channelCode": "123456789"
}
}
```
## Creating a Quote
**Important Notes:**
* All amounts are in Kobo (1 NGN = 100 Kobo)
* Minimum payout amount is 2000 NGN
* To send 3000 NGN, use amount = 300000 Kobo
```bash theme={null}
curl --request POST \
--url https://staging.api.mavapay.co/api/v1/quote \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: your_api_key_here' \
--data '{
"amount": 300000,
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "NGNKOBO",
"autopayout": true,
"beneficiary": {
"bankAccountNumber": "0149203789",
"bankAccountName": "olaolu olajide",
"bankCode": "000013",
"bankName": "GTBANK PLC"
}
}'
```
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"id": "d1614a76-9c7f-4111-98a7-43199f307e5b",
"exchangeRate": 5000000.23,
"usdToTargetCurrencyRate": 1500.65,
"sourceCurrency": "BTCSAT",
"targetCurrency": "NGNKOBO",
"transactionFeesInSourceCurrency": 13.23,
"transactionFeesInTargetCurrency": "3750.00",
"amountInSourceCurrency": 1499,
"amountInTargetCurrency": "50000.00",
"paymentMethod": "LIGHTNING",
"expiry": "2021-07-01T12:00:00Z",
"isValid": true,
"invoice": "lntbs36020n1pnehfzcpp5zc8g9tfpwnmaevh62ak2vu6zjqyt5cpph3gjm4zfyqz9srjjdsaqdz5296k7ar98gsy6ctkvys9qcteyqmnyenr8yunswfdvyer2v3dx33kxe3dvymnsefdv5mnjcm9x5mrydm9vccqcqzpuxqzfvsp5utd7hdfmjp7vw3lc6hal8psqruwcnu89hxfukyzugxh40jhv8xcq9qxpqysgq2lry5dgzu2g04s74tpvctwe37rkprs3k579jw6yt7ncrsg62p29yalw8gf5fawfvpydqv4jjve0nsysfhsxa35dtwug9e6gzxkq0h5gp636wc3",
"hash": "189727ef2e0f35921af7858b96e27cbad960b0ec2bb2a4ab73b6a231f9ce8727",
"totalAmountInSourceCurrency": 1499,
"customerInternalFee": 0,
"createdAt": "2021-07-01T12:00:00Z",
"updatedAt": "2021-07-01T12:00:00Z"
}
}
```
### Understanding the Quote Response
* `usdToTargetCurrencyRate`: Price of 1 USD in NGN
* `exchangeRate`: Price of 1 BTC in NGN
* `amountInSourceCurrency`: Total amount in SATS to be paid (including fees)
* `amountInTargetCurrency`: Total amount in Kobo to be sent to recipient
* `transactionFeesInSourceCurrency`: Fees in SATS (included in amountInSourceCurrency)
* `transactionFeesInTargetCurrency`: Fees in Kobo
* `expiry`: Invoice validity (5 minutes)
* `customerInternalFee`: Optional extra fee you can charge (in SATS)
## Webhook Events
You'll receive two webhook events:
1. `payment.received` - When the lightning payment is received
2. `payment.sent` - When the money is sent to the recipient's bank account
### payment.received Event
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "0b234e32-d5ba-4c2a-8f71-0b820a477f01",
"walletId": "b7e7b823-b572-41d3-86a6-a6872f35c274",
"ref": "f05432ecab06ca0357596af0366de",
"hash": "001f7fb879bb3874eb057dcce06437d45d4020d7f348a36cd583b658e98e07d6",
"amount": 300000,
"fees": 2500,
"currency": "NGN",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-17T13:20:20.575Z",
"updatedAt": "2025-01-17T13:20:20.575Z",
"transactionMetadata": {
"id": "570cba51-d9fd-402a-8b10-5c43e23077d8",
"orderId": "82958-2591",
"bankCode": "09026",
"bankName": "KUDA MICROFINANCE BANK",
"bankAccountName": "OLADEJI OLAOLU",
"bankAccountNumber": "0123456789",
"customerInternalFee": 0,
"createdAt": "2025-02-03T14:18:20.737Z",
"updatedAt": "2025-02-03T14:18:22.777Z"
}
}
}
```
### payment.sent Event
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "e6291848-a632-44c8-8398-bf318bbc17de",
"walletId": "b7e7b823-b572-41d3-86a6-a6872f35c274",
"ref": "06679ad012a143407d1685561dd27",
"hash": "001f7fb879bb3874eb057dcce06437d45d4020d7f348a36cd583b658e98e07d6",
"amount": 300000,
"fees": 0,
"currency": "NGN",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-17T13:20:21.126Z",
"updatedAt": "2025-01-17T13:20:23.476Z",
"transactionMetadata": {
"id": "570cba51-d9fd-402a-8b10-5c43e23077d8",
"orderId": "82958-2591",
"bankCode": "09026",
"bankName": "KUDA MICROFINANCE BANK",
"bankAccountName": "OLADEJI OLAOLU",
"bankAccountNumber": "0123456789",
"customerInternalFee": 0,
"createdAt": "2025-02-03T14:18:20.737Z",
"updatedAt": "2025-02-03T14:18:22.777Z"
}
}
}
```
## Checking Transaction Status
You can [query transactions](/api-reference/endpoint/transaction/get-transaction) using the payment hash, orderId, or transaction ID:
```bash theme={null}
# Using hash
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?hash=08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba' \
--header 'x-api-key: your_api_key_here'
# Using transaction ID
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?id=7bac35f1-2efc-448b-8a71-0d071da43315' \
--header 'x-api-key: your_api_key_here'
```
## Getting Price Rates
To get current NGN rates:
```bash theme={null}
curl --location 'https://staging.api.mavapay.co/api/v1/price?currency=NGN'
```
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"currency": "NGN",
"timestamp": 1738258609,
"btcPriceInUnitCurrency": 167792701.75015488,
"unitPricePerSat": {
"amount": 1.6779270175015488,
"currencyUnit": "NGNSAT"
},
"unitPricePerUsd": {
"amount": 1596.29641,
"currencyUnit": "NGNUSD"
}
}
}
```
## Testing
Use our staging environment for testing:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
Test bank account details:
* Account Number: `0149200000`
* Bank: GTBANK PLC
* Bank Code: `000013`
**Note**: All test environment invoices are signet invoices.
## Support
If you need help with integration:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# South African Rand (ZAR) Payouts
Source: https://docs.mavapay.co/payouts/zar-payout
Learn how to integrate Mavapay payouts for South African Rand (ZAR)
This guide explains how to integrate Mavapay's payout functionality for sending money to South African bank accounts.
## Overview
The ZAR payout process involves the following steps:
1. Get the list of supported South African banks
2. Create a quote for the amount you want to pay out
3. Pay the generated lightning invoice
4. Receive webhook notifications for payment status
## Prerequisites
Before you begin, ensure you have:
1. Created a Mavapay account
2. Obtained your API key
3. Set up your webhook endpoint
4. Verified your business account
## Authentication
All API requests must include your API key in the header:
```bash theme={null}
X-API-KEY: your_api_key_here
```
## Getting Bank List
First, [retrieve the list of supported South African banks](/api-reference/endpoint/bank-info/bank-code):
```bash theme={null}
curl --location 'https://staging.api.mavapay.co/api/v1/bank/bankcode?country=ZA' \
--header 'x-api-key: your_api_key_here'
```
## Creating a Quote
**Important Notes:**
* All amounts are in cents (1 ZAR = 100 cents)
* To send 3,000 ZAR, use amount = 300000 cents
* The minimum amount for ZAR payouts is \$2
```bash theme={null}
curl --request POST \
--url https://staging.api.mavapay.co/api/v1/quote \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: your_api_key_here' \
--data '{
"amount": 300000,
"sourceCurrency": "BTCSAT",
"targetCurrency": "ZARCENT",
"paymentMethod": "LIGHTNING",
"paymentCurrency": "ZARCENT",
"autopayout": true,
"beneficiary": {
"name": "Ricki Tester",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1352900000"
}
}'
```
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"id": "d1614a76-9c7f-4111-98a7-43199f307e5b",
"exchangeRate": 1874805.7499999998,
"usdToTargetCurrencyRate": 18.4977,
"sourceCurrency": "BTCSAT",
"targetCurrency": "ZARCENT",
"transactionFeesInSourceCurrency": 13.23,
"transactionFeesInTargetCurrency": "3750.00",
"amountInSourceCurrency": 1499,
"amountInTargetCurrency": "50000.00",
"paymentMethod": "LIGHTNING",
"expiry": "2021-07-01T12:00:00Z",
"isValid": true,
"invoice": "lntbs36020n1pnehfzcpp5zc8g9tfpwnmaevh62ak2vu6zjqyt5cpph3gjm4zfyqz9srjjdsaqdz5296k7ar98gsy6ctkvys9qcteyqmnyenr8yunswfdvyer2v3dx33kxe3dvymnsefdv5mnjcm9x5mrydm9vccqcqzpuxqzfvsp5utd7hdfmjp7vw3lc6hal8psqruwcnu89hxfukyzugxh40jhv8xcq9qxpqysgq2lry5dgzu2g04s74tpvctwe37rkprs3k579jw6yt7ncrsg62p29yalw8gf5fawfvpydqv4jjve0nsysfhsxa35dtwug9e6gzxkq0h5gp636wc3",
"hash": "189727ef2e0f35921af7858b96e27cbad960b0ec2bb2a4ab73b6a231f9ce8727",
"totalAmountInSourceCurrency": 1499,
"customerInternalFee": 0,
"createdAt": "2021-07-01T12:00:00Z",
"updatedAt": "2021-07-01T12:00:00Z"
}
}
```
### Understanding the Quote Response
* `usdToTargetCurrencyRate`: Price of 1 USD in ZAR
* `exchangeRate`: Price of 1 BTC in ZAR
* `amountInSourceCurrency`: Total amount in SATS to be paid (including fees)
* `amountInTargetCurrency`: Total amount in cents to be sent to recipient
* `transactionFeesInSourceCurrency`: Fees in SATS (included in amountInSourceCurrency)
* `transactionFeesInTargetCurrency`: Fees in cents
* `expiry`: Invoice validity (5 minutes)
* `customerInternalFee`: Currently disabled for ZAR payments
## Webhook Events
You'll receive two webhook events:
1. `payment.received` - When the lightning payment is received
2. `payment.sent` - When the money is sent to the recipient's bank account
### payment.received Event
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "0b234e32-d5ba-4c2a-8f71-0b820a477f01",
"walletId": "b7e7b823-b572-41d3-86a6-a6872f35c274",
"ref": "f05432ecab06ca0357596af0366de",
"hash": "001f7fb879bb3874eb057dcce06437d45d4020d7f348a36cd583b658e98e07d6",
"amount": 300000,
"fees": 2500,
"currency": "ZAR",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-17T13:20:20.575Z",
"updatedAt": "2025-01-17T13:20:20.575Z",
"transactionMetadata": {
"id": "570cba51-d9fd-402a-8b10-5c43e23077d8",
"orderId": "82958-2591",
"name": "Ricki Allardice",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1352906218",
"customerInternalFee": 0,
"createdAt": "2025-02-03T14:18:20.737Z",
"updatedAt": "2025-02-03T14:18:22.777Z"
}
}
}
```
### payment.sent Event
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "e6291848-a632-44c8-8398-bf318bbc17de",
"walletId": "b7e7b823-b572-41d3-86a6-a6872f35c274",
"ref": "06679ad012a143407d1685561dd27",
"hash": "001f7fb879bb3874eb057dcce06437d45d4020d7f348a36cd583b658e98e07d6",
"amount": 300000,
"fees": 0,
"currency": "ZAR",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-17T13:20:21.126Z",
"updatedAt": "2025-01-17T13:20:23.476Z",
"transactionMetadata": {
"id": "570cba51-d9fd-402a-8b10-5c43e23077d8",
"orderId": "82958-2591",
"name": "Ricki Allardice",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1352906218",
"customerInternalFee": 0,
"createdAt": "2025-02-03T14:18:20.737Z",
"updatedAt": "2025-02-03T14:18:22.777Z"
}
}
}
```
## Checking Transaction Status
You can [query transactions](/api-reference/endpoint/transaction/get-transaction) using the payment hash, orderId, or transaction ID:
```bash theme={null}
# Using hash
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?hash=08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba' \
--header 'x-api-key: your_api_key_here'
# Using transaction ID
curl --location 'https://staging.api.mavapay.co/api/v1/transaction?id=7bac35f1-2efc-448b-8a71-0d071da43315' \
--header 'x-api-key: your_api_key_here'
```
## Getting Price Rates
To get current ZAR rates:
```bash theme={null}
curl --location 'https://staging.api.mavapay.co/api/v1/price?currency=ZAR'
```
Sample response:
```json theme={null}
{
"status": "ok",
"data": {
"currency": "ZAR",
"timestamp": 1738258609,
"btcPriceInUnitCurrency": 1874805.7499999998,
"unitPricePerSat": {
"amount": 0.01874805,
"currencyUnit": "ZARSAT"
},
"unitPricePerUsd": {
"amount": 18.4977,
"currencyUnit": "ZARUSD"
}
}
}
```
## Testing
Use our staging environment for testing:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
Test bank account details:
* Account Number: `1352900000`
* Bank: CAPITEC BANK
* Account Holder: Ricki Tester
**Note**: All test environment invoices are signet invoices.
## Support
If you need help with integration:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# Stablecoins
Source: https://docs.mavapay.co/stablecoins/introduction
Deposit and withdraw USDT using your Mavapay stablecoin wallet
Mavapay provides a USDT wallet that you can use to collect stablecoin deposits from customers and send USDT to external addresses.
## Overview
USDT balances live in your Mavapay **USDT wallet**, separate from your BTC and fiat wallets. You can:
1. [Generate a deposit address](/api-reference/endpoint/collection/generate-stablecoin-address) on a supported network
2. Share that address with your customer
3. Receive a `payment.received` webhook when the deposit is credited (or `FAILED` if it cannot be credited)
4. [Withdraw USDT](/api-reference/endpoint/withdraw/withdraw-stablecoin) to any address on a supported network
Check your USDT balance with the [Get wallets](/api-reference/endpoint/wallet/get-wallets) endpoint (`currency=USDT`).
## Supported Networks
| Network | Request value | Address format |
| ---------------- | ------------- | ------------------------------- |
| Tron (TRC20) | `trc20` | Tron address (`T...`) |
| Ethereum (ERC20) | `erc20` | Ethereum address (`0x...`) |
| Plasma | `plasma` | Plasma address |
| Solana | `solana` | Solana address |
| Polygon | `polygon` | Polygon / EVM address (`0x...`) |
Use **lowercase** network values in requests (`erc20`, `trc20`). Address objects may return the network in uppercase (`ERC20`); webhook metadata uses lowercase (`erc20`).
Send USDT only on the network the address was generated for. USDT sent on the wrong chain cannot be recovered.
## Amounts
USDT amounts in API requests, wallet balances, and webhooks are in **cents** (2 decimal places). 1 USDT = 100 cents:
| Amount | USDT |
| -------- | ---------- |
| `100` | 1 USDT |
| `500` | 5 USDT |
| `1000` | 10 USDT |
| `500000` | 5,000 USDT |
Deposit address `minAmount` and `maxAmount` are returned in **USDT**, not cents. For example, `"minAmount": "5"` means deposits below 5 USDT may not be credited. `"maxAmount": "0"` means there is no maximum.
## Deposit USDT
### 1. Generate a deposit address
Call [Generate a stablecoin deposit address](/api-reference/endpoint/collection/generate-stablecoin-address) with a `label` so you can identify the address later. You can create multiple addresses per network.
```bash theme={null}
curl --request POST \
--url https://api.mavapay.co/api/v1/collections/stablecoin/generate-address \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"label": "test 1",
"currency": "USDT",
"network": "erc20"
}'
```
Sample response:
```json theme={null}
{
"status": "ok",
"message": "Address generated successfully",
"data": {
"id": "bdf9bbba-f562-4b10-abfb-b180404415b7",
"address": "0x220303f911dbC38F09d13f8cC3ba8700f0Ca8C99",
"currency": "USDT",
"network": "ERC20",
"memo": null,
"reference": "test 1",
"minAmount": "5",
"maxAmount": "0",
"walletId": "69a7129f-0a0a-484d-968f-2601bce85149",
"updatedAt": "2026-08-20T10:09:46.288Z",
"createdAt": "2026-08-20T10:09:46.288Z"
}
}
```
| Field | Description |
| ----------- | ---------------------------------------------------------------------------------------------------- |
| `address` | Share this with the customer. They must send USDT on the matching network. |
| `memo` | Include this with the transfer when it is not `null`. Most supported networks do not require a memo. |
| `reference` | The `label` you supplied when generating the address. |
| `minAmount` | Minimum deposit in USDT. |
| `walletId` | The USDT wallet that will be credited. |
### 2. List existing addresses
Retrieve previously generated addresses with [List stablecoin deposit addresses](/api-reference/endpoint/collection/get-stablecoin-addresses):
```bash theme={null}
curl --request GET \
--url 'https://api.mavapay.co/api/v1/collections/stablecoin/addresses?currency=USDT' \
--header 'x-api-key: YOUR_API_KEY'
```
Sample response:
```json theme={null}
{
"status": "ok",
"message": "Deposit addresses retrieved successfully",
"data": [
{
"id": "41abd3c5-4fcd-4e8a-8c74-5702bb758f80",
"walletId": "69a7129f-0a0a-484d-968f-2601bce85149",
"address": "0xAdEd4E29eB636A5c6500c881812701eB0a7043A0",
"network": "ERC20",
"currency": "USDT",
"memo": null,
"reference": "test 1",
"minAmount": "5",
"maxAmount": "0",
"createdAt": "2026-06-09T09:24:38.785Z",
"updatedAt": "2026-06-09T09:24:38.785Z"
}
]
}
```
### 3. Wait for the deposit webhook
After the transfer confirms on-chain, Mavapay core credits your USDT wallet and sends a `payment.received` webhook with `currency: "USDT"` and `type: "DEPOSIT"`.
* `status: "SUCCESS"` — the deposit is available in your USDT wallet
* `status: "FAILED"` — the deposit was not credited. This can happen if the sending address is blacklisted, or in rare undocumented cases
Use the transaction `id` for idempotency. Match the deposit to a customer with `stablecoinTransactionMetadata.address` or `reference`.
See [USDT webhook examples](/webhooks/webhook-examples#usdt-webhooks).
## Withdraw USDT
Withdraw from your USDT wallet to any address on a supported network.
```bash theme={null}
curl --request POST \
--url https://api.mavapay.co/api/v1/withdraw/stablecoin \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"amount": "500000",
"network": "trc20",
"currency": "USDT",
"address": "TLt6fwdcdD18S36L9oiikn4Z8hebgwbzwS",
"customerReference": "customer-provided-reference"
}'
```
| Field | Required | Description |
| ------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `amount` | Yes | Amount in USDT cents (2 decimal places). `"500000"` = 5,000 USDT |
| `network` | Yes | `trc20`, `erc20`, `plasma`, `solana`, or `polygon` |
| `currency` | Yes | Must be `USDT` |
| `address` | Yes | Destination address on the selected network |
| `customerReference` | No | Your identifier for this withdrawal. Returned on webhooks as `stablecoinTransactionMetadata.customerReference` |
The destination `address` must be valid for the `network` you pass. A Tron address will fail on `erc20`, and an Ethereum address will fail on `trc20`.
You receive two `payment.sent` webhooks for a withdrawal:
1. **Initiated** — `status: "PENDING"` and `hash` is `null`
2. **Completed** — `status: "SUCCESS"` and `hash` is populated
See [USDT webhook examples](/webhooks/webhook-examples#usdt-webhooks).
## Webhooks
| Event | When |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `payment.received` | Deposit processed by Mavapay core. `SUCCESS` if credited; `FAILED` if it cannot be credited (for example a blacklisted sending address) |
| `payment.sent` | Withdrawal initiated (`PENDING`, `hash` is `null`) and again when it completes (`SUCCESS`, `hash` is set) |
Register your webhook endpoint before going live. See [Webhook integration](/webhooks/introduction) and [USDT webhook examples](/webhooks/webhook-examples#usdt-webhooks).
## Testing
Use the staging environment while you integrate:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
## Next steps
Create a USDT address on a supported network
Send USDT from your Mavapay wallet
Check your USDT wallet balance
Handle deposit and withdrawal events
# Introduction
Source: https://docs.mavapay.co/webhooks/introduction
Learn how to integrate and handle Mavapay webhooks
# Webhook Integration Guide
Webhooks allow your application to receive real-time notifications about events that occur in your Mavapay account. This guide explains how to integrate, secure, and handle webhook notifications.
## Overview
Mavapay uses webhooks to notify your application when events happen in your account. Instead of polling our API, webhooks enable your application to be notified in real-time when events occur.
## Setting Up Webhooks
### 1. Register Your Webhook Endpoint
First, [register your webhook](/api-reference/endpoint/webhook/register-webhook) endpoint using our API:
```javascript theme={null}
const response = await fetch('https://api.mavapay.co/api/v1/webhook/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'your_api_key_here'
},
body: JSON.stringify({
url: 'https://your-domain.com/webhook',
secret: 'your_webhook_secret'
})
});
```
### 2. Update Webhook Configuration
You can update your webhook configuration at any time:
```javascript theme={null}
const response = await fetch('https://api.mavapay.co/api/v1/webhook', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'your_api_key_here'
},
body: JSON.stringify({
url: 'https://your-new-domain.com/webhook',
secret: 'your_new_webhook_secret'
})
});
```
## Event Types
Mavapay currently supports the following webhook events:
1. `payment.received` - Triggered when a payment is received (including USDT deposits). Status may be `SUCCESS` or `FAILED`.
2. `payment.sent` - Triggered when a payment is sent (including USDT withdrawals). USDT withdrawals fire once as `PENDING` (`hash` is `null`) and again as `SUCCESS` when complete.
### Event Payloads
#### payment.received
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "tx_123456789",
"amount": 500000,
"currency": "NGN",
"status": "SUCCESS",
"metadata": {
"hash": "189727ef2e0f35921af7858b96e27cbad960b0ec2bb2a4ab73b6a231f9ce8727",
"reference": "MP123456789",
...
}
}
}
```
#### payment.sent
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "tx_123456789",
"amount": 500000,
"currency": "NGN",
"status": "SUCCESS",
"metadata": {
"bankAccountNumber": "0149203789",
"bankAccountName": "John Doe",
"bankName": "GTBANK PLC",
"reference": "MP123456789",
...
}
}
}
```
## Security
### Verifying Webhook Signatures
To ensure the webhook requests are coming from Mavapay, we sign each request with your webhook secret. The signature is included in the `X-Mavapay-Signature` header.
Here's how to verify the signature (Node.js example):
```javascript theme={null}
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-mavapay-signature'];
const isValid = verifyWebhookSignature(
req.body,
signature,
'your_webhook_secret'
);
if (!isValid) {
return res.status(400).json({ error: 'Invalid signature' });
}
// Process the webhook
const { event, data } = req.body;
// Handle the event...
res.json({ received: true });
});
```
## Best Practices
1. **Verify Signatures**: Always verify webhook signatures to ensure requests are from Mavapay.
2. **Idempotency**: Process webhooks idempotently using the event ID to avoid duplicate processing.
3. **Quick Response**: Respond to webhooks quickly (within 5 seconds) to acknowledge receipt.
4. **Retry Logic**: Implement retry logic in case your endpoint is temporarily unavailable.
5. **Logging**: Log webhook requests for debugging and auditing purposes.
6. **Error Handling**: Implement proper error handling for failed webhook processing.
## Testing Webhooks
You can use our staging environment to test webhooks:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
We recommend using tools like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) for local development.
### Test Event
You can trigger a test event to verify your webhook setup:
```javascript theme={null}
const response = await fetch('https://api.mavapay.co/api/v1/webhook/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'your_api_key_here'
}
});
```
## Troubleshooting
Common issues and solutions:
1. **Invalid Signature**: Double-check your webhook secret and signature verification code.
2. **Timeout**: Ensure your endpoint responds within 5 seconds.
3. **SSL Error**: Make sure your endpoint has a valid SSL certificate.
4. **Wrong Content-Type**: Your endpoint should accept `application/json`.
## Support
If you need help with webhooks:
* Email: [info@mavapay.co](mailto:info@mavapay.co)
* Documentation: [https://docs.mavapay.co](https://docs.mavapay.co)
* API Reference: [https://docs.mavapay.co/api-reference](https://docs.mavapay.co/api-reference)
# Webhook Examples
Source: https://docs.mavapay.co/webhooks/webhook-examples
Complete webhook payload examples for all supported currencies
# Webhook Payload Examples
This guide provides complete webhook payload examples for each supported currency. Mavapay sends webhooks for two main event types: `payment.received` (DEPOSIT) and `payment.sent` (WITHDRAWAL).
## Event Types
| Event | Type | Description |
| ---------------------- | ---------- | ---------------------------------------------------------- |
| `payment.received` | DEPOSIT | Triggered when Mavapay receives funds from a customer |
| `payment.sent` | WITHDRAWAL | Triggered when Mavapay sends funds to a beneficiary |
| `payment_link.settled` | SETTLEMENT | Triggered when a Payment Link transaction is fully settled |
## Bitcoin (BTC) Webhooks
### payment.received (Deposit)
Triggered when Bitcoin payment is received via Lightning Network.
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "9830a229-1db0-4a19-9e51-37cca51586f2",
"walletId": "98b5093f-6913-48c6-acb4-8fdc46c7feec",
"ref": "678ae2e20de0dee7aecd2bd40f8c9",
"hash": "6834d0d46ce47f0012cb36ca",
"amount": 3427,
"fees": 17,
"currency": "BTC",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-05-26T20:37:23.566Z",
"updatedAt": "2025-05-26T20:37:23.566Z",
"btcUsdMetadata": {
"id": "698daefd-1dd8-4420-9c0a-5d78080d7c27",
"orderId": "58772-8830",
"paymentMethod": "BANKTRANSFER",
"customerReference": "order-1234567890",
"customerInternalFee": 0,
"estimatedRoutingFee": 0,
"onChainAddress": null,
"lnInvoice": "lntbs34270n1p5rf5rlpp56xqq6cj9ml75v0yl6fsfrlmwfmaygnal8gd706l3epzethymhclsdz5296...",
"lnAddress": null,
"createdAt": "2025-05-26T20:37:23.576Z",
"updatedAt": "2025-05-26T20:37:23.576Z"
}
}
}
```
### Key Fields
| Field | Description |
| -------------------------- | ----------------------------------- |
| `amount` | Amount in satoshis |
| `fees` | Transaction fees in satoshis |
| `hash` | Payment hash for tracking |
| `btcUsdMetadata.lnInvoice` | The Lightning invoice that was paid |
| `btcUsdMetadata.orderId` | Associated order ID |
### payment.sent (On-Chain Withdrawal)
Triggered when Bitcoin is sent on-chain to a beneficiary address.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "220b23dc-38f7-413c-b2d1-b7f765c50add",
"walletId": "40aa2b67-1498-449d-85f2-17b93eab2d74",
"ref": "19d4793744af1e9fc77cb64d374d6",
"hash": "691f836d4e7ce000125478be",
"amount": 6000,
"fees": 0,
"currency": "BTC",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-11-20T21:10:12.180Z",
"updatedAt": "2025-11-20T23:35:52.986Z",
"btcUsdMetadata": {
"id": "b6d82314-0148-413e-bfad-ccd74a45969e",
"orderId": "44092-3881",
"paymentMethod": "BANKTRANSFER",
"customerReference": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"lnInvoice": null,
"lnAddress": null,
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"onchainTxid": "5c5df6db67a8fbe684d48560fe151a0ecfade509d640bd21b6a787707b972f62",
"onchainNetworkSpeed": "medium",
"createdAt": "2025-11-20T21:10:12.185Z",
"updatedAt": "2025-11-20T23:35:53.036Z"
}
}
}
```
### On-Chain Specific Fields
| Field | Description |
| ------------------------------------ | -------------------------------------------------------- |
| `btcUsdMetadata.onChainAddress` | The Bitcoin address that received the payment |
| `btcUsdMetadata.onchainTxid` | The Bitcoin transaction ID (viewable on block explorers) |
| `btcUsdMetadata.onchainNetworkSpeed` | Fee priority used: `slow`, `medium`, or `fast` |
On-chain withdrawals remain in `PENDING` status until there are **3 block confirmations**, after which the status changes to `SUCCESS`.
## USDT Webhooks
USDT deposits and withdrawals use the same `payment.received` and `payment.sent` events as other currencies. Metadata is on `stablecoinTransactionMetadata`. The transaction `currency` is `"USDT"`.
Amounts are in USDT cents (2 decimal places). Divide by `100` to get USDT. For example, `1000` = 10 USDT, `300` = 3 USDT.
### payment.received (Deposit)
Fired after the on-chain deposit has been processed by Mavapay core.
* `status: "SUCCESS"` — the funds are available in your USDT wallet
* `status: "FAILED"` — the deposit was not credited. This can happen if the sending address is blacklisted, or in rare undocumented cases
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "ae30046c-27d5-4d12-b4b1-83782c934fc9",
"walletId": "206132f7-ed30-43fa-8f5a-e436aaf948cc",
"ref": "019ff3a2-4468-7cc2-a1e2-1c2b49fae1c4",
"hash": "7b69613664e79423c4b5f1e3dcb23",
"amount": 1000,
"fees": 0,
"currency": "USDT",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": false,
"createdAt": "2026-08-13T12:13:53.509Z",
"updatedAt": "2026-08-13T12:15:35.646Z",
"stablecoinTransactionMetadata": {
"id": "f25622be-8f64-4d0e-85a2-57443c9cbbc8",
"orderId": null,
"beneficiaryId": null,
"address": "0xC3a0dA7aB9e033801aE2531757526A156601b8C9",
"network": "erc20",
"currency": "USDT",
"memo": null,
"reference": "019ff3a2-4468-7cc2-a1e2-1c2b49fae1c4",
"customerReference": null,
"customerInternalFee": 0,
"createdAt": "2026-08-13T12:13:53.525Z",
"updatedAt": "2026-08-13T12:13:53.525Z"
}
}
}
```
### Key Fields
| Field | Description |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| `amount` | Amount in USDT cents (`1000` = 10 USDT) |
| `status` | `SUCCESS` if credited, `FAILED` if the deposit could not be credited |
| `hash` | Set once the deposit has been processed |
| `stablecoinTransactionMetadata.network` | Network the deposit arrived on (`erc20`, `trc20`, `plasma`, `solana`, `polygon`) |
| `stablecoinTransactionMetadata.address` | The collection address that received the funds |
| `stablecoinTransactionMetadata.reference` | Identifier for this deposit address |
A `FAILED` deposit means the on-chain transfer was not credited to your wallet. Handle `FAILED` the same way you handle `SUCCESS`: acknowledge the webhook and update the customer. Do not retry crediting the funds yourself.
### payment.sent (Withdrawal initiated)
Fired when you call [Withdraw stablecoin](/api-reference/endpoint/withdraw/withdraw-stablecoin). The transfer is not on-chain yet: `status` is `PENDING` and `hash` is `null`.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "01146747-640a-46c6-8023-c2d20f734ea3",
"walletId": "206132f7-ed30-43fa-8f5a-e436aaf948cc",
"ref": "a77d683ed70fca06a82969ec58c1a",
"hash": null,
"amount": 300,
"fees": 40,
"currency": "USDT",
"type": "WITHDRAWAL",
"status": "PENDING",
"autopayout": false,
"createdAt": "2026-08-13T12:38:08.958Z",
"updatedAt": "2026-08-13T12:38:08.958Z",
"stablecoinTransactionMetadata": {
"id": "609f20bb-5005-491d-9e8c-1b3454271d96",
"orderId": null,
"beneficiaryId": null,
"address": "0xC0508cBB551e0d7aB31D1b341591197c93e92B1F",
"network": "erc20",
"currency": "USDT",
"memo": null,
"reference": "a77d683ed70fca06a82969ec58c1a",
"customerReference": "customer-provided-reference",
"customerInternalFee": 0,
"createdAt": "2026-08-13T12:38:08.979Z",
"updatedAt": "2026-08-13T12:38:08.979Z"
}
}
}
```
### payment.sent (Withdrawal completed)
Fired when the withdrawal is completed. `status` is `SUCCESS` and `hash` is now set. The transaction `id` is the same as the pending webhook.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "01146747-640a-46c6-8023-c2d20f734ea3",
"walletId": "206132f7-ed30-43fa-8f5a-e436aaf948cc",
"ref": "a77d683ed70fca06a82969ec58c1a",
"hash": "c56dc34e8aedfd4ce737ba6092fed",
"amount": 300,
"fees": 40,
"currency": "USDT",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": false,
"createdAt": "2026-08-13T12:38:08.958Z",
"updatedAt": "2026-08-13T12:38:09.045Z",
"stablecoinTransactionMetadata": {
"id": "609f20bb-5005-491d-9e8c-1b3454271d96",
"orderId": null,
"beneficiaryId": null,
"address": "0xC0508cBB551e0d7aB31D1b341591197c93e92B1F",
"network": "erc20",
"currency": "USDT",
"memo": null,
"reference": "a77d683ed70fca06a82969ec58c1a",
"customerReference": "customer-provided-reference",
"customerInternalFee": 0,
"createdAt": "2026-08-13T12:38:08.979Z",
"updatedAt": "2026-08-13T12:38:08.979Z"
}
}
}
```
### Withdrawal Fields
| Field | Description |
| ------------------------------------------------- | --------------------------------------------------------- |
| `amount` | Amount in USDT cents (`300` = 3 USDT) |
| `fees` | Withdrawal fee in USDT cents (`40` = 0.40 USDT) |
| `hash` | `null` while `PENDING`; set when the withdrawal completes |
| `status` | `PENDING` on initiate, `SUCCESS` when complete |
| `stablecoinTransactionMetadata.address` | Destination address |
| `stablecoinTransactionMetadata.network` | Network used for the withdrawal |
| `stablecoinTransactionMetadata.customerReference` | Your `customerReference` from the withdraw request |
Treat the pending and completed `payment.sent` events as the same withdrawal (`data.id` does not change). Use idempotency so you do not double-process the completed event.
## Payment Link Webhooks
### payment\_link.settled
Triggered when a Payment Link transaction is fully settled (after block confirmations for on-chain).
```json theme={null}
{
"event": "payment_link.settled",
"data": {
"paymentRef": "1c50bb6a-9f49-493c-86b5-75a9e790f7c9",
"paymentLink": "https://checkout.mavapay.co/1c50bb6a-9f49-493c-86b5-75a9e790f7c9",
"callbackUrl": "https://your-app.com/callback",
"settlementCurrency": "BTC",
"paymentCurrency": "BTC",
"paymentMethod": "BANKTRANSFER",
"transaction": {
"id": "220b23dc-38f7-413c-b2d1-b7f765c50add",
"walletId": "40aa2b67-1498-449d-85f2-17b93eab2d74",
"ref": "19d4793744af1e9fc77cb64d374d6",
"hash": "691f836d4e7ce000125478be",
"amount": 6000,
"fees": 0,
"currency": "BTC",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-11-20T21:10:12.180Z",
"updatedAt": "2025-11-20T23:35:52.986Z",
"btcUsdMetadata": {
"id": "b6d82314-0148-413e-bfad-ccd74a45969e",
"orderId": "44092-3881",
"paymentMethod": "BANKTRANSFER",
"customerReference": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"onChainAddress": "bc1q8yz7u50r4nne2q8wkuc4kyxx0s76fxsjlac5fxd8h7tzkh4rs82qnweuv5",
"lnInvoice": null,
"lnAddress": null,
"onchainTxid": "5c5df6db67a8fbe684d48560fe151a0ecfade509d640bd21b6a787707b972f62",
"onchainNetworkSpeed": "medium",
"createdAt": "2025-11-20T21:10:12.185Z",
"updatedAt": "2025-11-20T23:35:53.036Z"
}
}
}
}
```
### Key Fields
| Field | Description |
| -------------------- | --------------------------------------------------- |
| `paymentRef` | Unique reference for the payment link |
| `paymentLink` | The checkout URL |
| `callbackUrl` | Your registered callback URL |
| `settlementCurrency` | The currency received (e.g., BTC) |
| `transaction` | Full transaction details including `btcUsdMetadata` |
## Nigerian Naira (NGN) Webhooks
### payment.received (Deposit)
Triggered when NGN payment is received in your Mavapay account.
```json theme={null}
{
"event": "payment.received",
"data": {
"id": "0b234e32-d5ba-4c2a-8f71-0b820a477f01",
"walletId": "b7e7b823-b572-41d3-86a6-a6872f35c274",
"ref": "f05432ecab06ca0357596af0366de",
"hash": "001f7fb879bb3874eb057dcce06437d45d4020d7f348a36cd583b658e98e07d6",
"amount": 300000,
"fees": 2500,
"currency": "NGN",
"type": "DEPOSIT",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-17T13:20:20.575Z",
"updatedAt": "2025-01-17T13:20:20.575Z",
"transactionMetadata": {
"id": "570cba51-d9fd-402a-8b10-5c43e23077d8",
"orderId": "82958-2591",
"bankCode": "09026",
"bankName": "KUDA MICROFINANCE BANK",
"bankAccountName": "OLADEJI OLAOLU",
"bankAccountNumber": "0123456789",
"customerInternalFee": 0,
"customerReference": "order-1234567890",
"createdAt": "2025-02-03T14:18:20.737Z",
"updatedAt": "2025-02-03T14:18:22.777Z"
}
}
}
```
### Key Fields
| Field | Description |
| --------------------------------------- | --------------------------------- |
| `amount` | Amount in kobo (1 NGN = 100 kobo) |
| `fees` | Transaction fees in kobo |
| `transactionMetadata.bankAccountName` | Sender's bank account name |
| `transactionMetadata.bankAccountNumber` | Sender's account number |
| `transactionMetadata.bankName` | Sender's bank name |
NGN amounts are in kobo. Divide by 100 to get the amount in Naira.
## South African Rand (ZAR) Webhooks
### payment.sent (Withdrawal)
Triggered when ZAR payment is sent to a beneficiary.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "14cfbe34-91b3-45ef-b1b0-bc7d55df7f0f",
"walletId": "99a8272d-c8d7-436b-952b-a13ef816df9c",
"ref": "d386f2c1260d53ad60bead81e1c99",
"hash": "08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba",
"amount": 5000,
"fees": 0,
"currency": "ZAR",
"type": "WITHDRAWAL",
"status": "PENDING",
"autopayout": true,
"createdAt": "2025-01-30T15:23:06.097Z",
"updatedAt": "2025-01-30T15:23:09.768Z",
"zarTransactionMetadata": {
"id": "7801c544-2f05-4914-91e1-d157848ac569",
"customerReference": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"orderId": "47581-9962",
"merchantName": "Ricki Allardice",
"bankName": "CAPITEC BANK",
"bankAccountNumber": "1352906218",
"reference": "MPYMX76EvJD2FYmUAkNd",
"customerInternalFee": 0,
"createdAt": "2025-01-30T15:23:06.099Z",
"updatedAt": "2025-01-30T15:23:09.772Z"
}
}
}
```
### Key Fields
| Field | Description |
| ------------------------------------------ | ----------------------------------- |
| `amount` | Amount in cents (1 ZAR = 100 cents) |
| `status` | Can be PENDING, SUCCESS, or FAILED |
| `zarTransactionMetadata.merchantName` | Beneficiary name |
| `zarTransactionMetadata.bankAccountNumber` | Beneficiary account number |
| `zarTransactionMetadata.reference` | Unique transaction reference |
ZAR amounts are in cents. Divide by 100 to get the amount in Rand.
## Kenyan Shilling (KES) Webhooks
### payment.sent (Withdrawal)
Triggered when KES payment is sent via mobile money (M-Pesa or Airtel) or M-Pesa paybill/till.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "14cfbe34-91b3-45ef-b1b0-bc7d55df7f0f",
"walletId": "99a8272d-c8d7-436b-952b-a13ef816df9c",
"ref": "d386f2c1260d53ad60bead81e1c99",
"hash": "08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba",
"amount": 5000,
"fees": 0,
"currency": "KES",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-30T15:23:06.097Z",
"updatedAt": "2025-01-30T15:23:09.768Z",
"kesTransactionMetadata": {
"id": "7801c544-2f05-4914-91e1-d157848ac569",
"customerReference": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"identifierType": "paytophone",
"identifiers": {
"phoneNumber": "+254796980711",
"accountName": "Nambanja David",
"network": "mpesa"
},
"createdAt": "2025-01-30T15:23:06.099Z",
"updatedAt": "2025-01-30T15:23:09.772Z"
}
}
}
```
### Key Fields
| Field | Description |
| --------------------------------------- | ---------------------------------------------------------------------------------------- |
| `amount` | Amount in cents (1 KES = 100 cents) |
| `kesTransactionMetadata.identifierType` | Payment type (paytophone, paytobill, paytotill) |
| `kesTransactionMetadata.identifiers` | Payment details (varies by type; paytophone includes `network`: `"mpesa"` or `"airtel"`) |
KES amounts are in cents. Divide by 100 to get the amount in Shillings.
## Ghanaian Cedi (GHS) Webhooks
### payment.sent (Withdrawal)
Triggered when GHS payment is sent via MTN Mobile Money.
```json theme={null}
{
"event": "payment.sent",
"data": {
"id": "14cfbe34-91b3-45ef-b1b0-bc7d55df7f0f",
"walletId": "99a8272d-c8d7-436b-952b-a13ef816df9c",
"ref": "d386f2c1260d53ad60bead81e1c99",
"hash": "08f7198fd7dd73c087b4754a20d6f63f5b87bb8fcdc10e4d5cacd27650fce2ba",
"amount": 5000,
"fees": 0,
"currency": "GHS",
"type": "WITHDRAWAL",
"status": "SUCCESS",
"autopayout": true,
"createdAt": "2025-01-30T15:23:06.097Z",
"updatedAt": "2025-01-30T15:23:09.768Z",
"ghsTransactionMetadata": {
"id": "7801c544-2f05-4914-91e1-d157848ac569",
"customerReference": "7e8cdec9-77c8-4bda-bf9f-2e96777b2c69",
"customerInternalFee": 0,
"identifierType": "MTN",
"identifiers": {
"phoneNumber": "+233999992800",
"accountName": "Mensah"
},
"createdAt": "2025-01-30T15:23:06.099Z",
"updatedAt": "2025-01-30T15:23:09.772Z"
}
}
}
```
### Key Fields
| Field | Description |
| --------------------------------------- | --------------------------------------- |
| `amount` | Amount in pesewa (1 GHS = 100 pesewa) |
| `ghsTransactionMetadata.identifierType` | Mobile money network identifier (`MTN`) |
| `ghsTransactionMetadata.identifiers` | Beneficiary payout details |
GHS amounts are in pesewa. Divide by 100 to get the amount in Cedis.
## Transaction Status Values
| Status | Description |
| --------- | ---------------------------------- |
| `PENDING` | Transaction is being processed |
| `SUCCESS` | Transaction completed successfully |
| `FAILED` | Transaction failed |
## Handling Webhooks
### 1. Verify Webhook Signature
Always verify the webhook signature to ensure it's from Mavapay:
```javascript theme={null}
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
```
### 2. Process Webhook Events
```javascript theme={null}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-mavapay-signature'];
const isValid = verifyWebhookSignature(
req.body,
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(400).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
switch (event) {
case 'payment.received':
// Handle deposit
handleDeposit(data);
break;
case 'payment.sent':
// Handle withdrawal
handleWithdrawal(data);
break;
default:
console.log(`Unhandled event type: ${event}`);
}
res.json({ received: true });
});
```
### 3. Implement Idempotency
Use the transaction ID to prevent duplicate processing:
```javascript theme={null}
function handleDeposit(data) {
const { id, amount, currency, status } = data;
// Check if already processed
if (alreadyProcessed(id)) {
console.log(`Transaction ${id} already processed`);
return;
}
// Process the deposit
processDeposit({ id, amount, currency, status });
// Mark as processed
markAsProcessed(id);
}
```
## Best Practices
1. **Respond Quickly**: Return a 200 status within 5 seconds to acknowledge receipt
2. **Process Asynchronously**: Handle heavy processing in background jobs
3. **Store Raw Payloads**: Log raw webhook payloads for debugging
4. **Handle Retries**: Implement idempotency to handle duplicate webhooks
5. **Monitor Failures**: Set up alerts for failed webhook deliveries
## Testing Webhooks
Use our staging environment to test webhook integration:
```bash theme={null}
https://staging.api.mavapay.co/api/v1
```
Consider using tools like:
* [webhook.site](https://webhook.site) for inspecting payloads
* [ngrok](https://ngrok.com) for local development
* [RequestBin](https://requestbin.com) for testing
## Next Steps
* Learn how to [register webhooks](/api-reference/endpoint/webhook/register-webhook)
* Explore [quote examples](/api-reference/quote-examples) for different currencies
* View [beneficiary formats](/api-reference/beneficiary-formats) for payment details