# Complete Account Registration
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/complete-account-registration
post /account/complete-registration
Complete a pending account registration request. This is used when merchant registration type is set to open
# Create Account
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/create-account
post /account
Create accounts.
# Create Bank Account
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/create-bank-account
put /account
Create bank account. You need to complete KYC before creating a bank account.
# Get Account
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/get-account
get /account
Get all accounts or filter using account name. The account name must be an alphanumeric lowercase hex string between 2 to 40 characters long
# Get Account Permissions
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/get-account-permissions
get /account/permissions
Get the valid permissions for accounts.
# Get user Account Profile
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/get-account-profile
get /account/myprofile
Get user account profile.
# Get Accounts Details
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/get-accounts-details
get /account/account-details
Get accounts details.
# Register Account
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/register-account
post /account/register
Create a pending account registration request. This is used when merchant registration type is set to open
# Search Account
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/search-account
get /account/search
Search matching account names using at least 2 characters
# Update Account Email
Source: https://docs.getpartna.com/api-reference/endpoint/v3/account/update-account-email
put /account/email
Update account email.
# Add IP to Whitelist
Source: https://docs.getpartna.com/api-reference/endpoint/v3/admin/add-ip-to-whitelist
post /user/ip-whitelist
Add ip address(es) to the whitelist.
# Get Whitelisted IPs
Source: https://docs.getpartna.com/api-reference/endpoint/v3/admin/get-whitelisted-ips
get /user/ip-whitelist
Retrieve all whitelisted ip addresses for a merchant.
# Remove IP from Whitelist
Source: https://docs.getpartna.com/api-reference/endpoint/v3/admin/remove-ip-from-whitelist
delete /user/ip-whitelist
Remove ip address from the whitelist.
# Get Banks
Source: https://docs.getpartna.com/api-reference/endpoint/v3/bank/get-banks
get /banks
Get supported banks by currency.
# Get Discounts
Source: https://docs.getpartna.com/api-reference/endpoint/v3/discount/get-discounts
get /discount
Get discounts applied on USD-NGN offramp transaction rates based on the transaction amount.
# Check for manual kyc
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/check-for-manual-kyc
get /user/kyc-exempt
Checks if the merchant is exempt from KYC requirements.
# Confirm KYC OTP
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/confirm-kyc-otp
put /kyc/confirm-otp
Confirm KYC verification OTP.
# Confirm Phone
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/confirm-phone
put /kyc/confirm-phone
Confirm phone number associated with account BVN.
# Handle BVN OTP Method
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/handle-bvn-otp-method-1
put /kyc/verification-method
Choose BVN verification method.
# Initiate BVN KYC
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/initiate-bvn-kyc
post /kyc
Initiate account KYC with BVN.
# Initiate user's phone number validation by country
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/initiate-users-phone-number-validation-by-country
post /phone
Initiate user's phone number validation by country
# Merchant KYC users manually
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/merchant-kyc-users-manually
post /kyc/complete-kyc
Allows a merchant to manually populate KYC information for their members.
# Populate address details for an existing NG KYC record
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/populate-address-details-for-an-existing-ng-kyc-record
put /kyc/address
Adds street address, city, and state to the other details for an existing NG KYC record.
# Resolve Bank
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/resolve-bank
post /kyc/resolve-account
Resolve bank account by account number and bank code.
# Supported Verification Methods by country
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/supported-verification-methods-by-country
get /kyc/supported-methods
Get supported KYC verification methods by country.
# Used to get a user's verified phone numbers by country
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/used-to-get-a-users-verified-phone-numbers-by-country
get /phone
Used to get a user's verified phone numbers by country.
# Validate a user's phone number
Source: https://docs.getpartna.com/api-reference/endpoint/v3/kyc/validate-a-users-phone-number
put /phone/verify
Validate a user's phone number
# Disable Account MFA
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mfa/disable-account-mfa
put /mfa/disable
Disable account MFA.
# Enable Account MFA
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mfa/enable-account-mfa
post /mfa
Initiate account MFA enrollment.
# Get Account MFA
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mfa/get-account-mfa
get /mfa
Get account MFA details.
# Validate Account MFA
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mfa/validate-account-mfa
put /mfa
Complete account MFA enrollment.
# Mock Deposit
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mock/mock-deposit
post /mock/deposit
Mock crypto and fiat deposit.
# Mock Fiat Deposit
Source: https://docs.getpartna.com/api-reference/endpoint/v3/mock/mock-fiat-deposit
post /mock/deposit-fiat
Mock fiat deposit.
# Get Pending Pricing
Source: https://docs.getpartna.com/api-reference/endpoint/v3/pricing/get-pending-pricing
get /pricing/pending
Get pending pricing.
# Pricing Alert
Source: https://docs.getpartna.com/api-reference/endpoint/v3/pricing/pricing-alert
get /pricing/trigger
Trigger pricing reminder, charge, retry, receipt or control-account reconciliation.
# Calculate ramp fees from toAmount or fromAmount
Source: https://docs.getpartna.com/api-reference/endpoint/v3/ramp/calculate-ramp-fees-from-toamount-or-fromamount
post /ramp/breakdown
Calculate ramp fees from toAmount or fromAmount
# Get Ramp Requests
Source: https://docs.getpartna.com/api-reference/endpoint/v3/ramp/get-ramp-requests
get /ramp
Gets ramp requests.
# Handle Ramp
Source: https://docs.getpartna.com/api-reference/endpoint/v3/ramp/handle-ramp
post /ramp/complete
Manual handler for completing a ramp request.
# Onramp and Offramp
Source: https://docs.getpartna.com/api-reference/endpoint/v3/ramp/onramp-and-offramp
post /ramp
Initiates onramp and offramp requests.
# Create a custom ratekey for conversion
Source: https://docs.getpartna.com/api-reference/endpoint/v3/rate/create-a-custom-ratekey-for-conversion
post /convert/preorder
Create a custom rate key to complete a conversion transaction.
# Get Rate
Source: https://docs.getpartna.com/api-reference/endpoint/v3/rate/get-rate
get /rate
Get Rate details.
# Verify Rate Key
Source: https://docs.getpartna.com/api-reference/endpoint/v3/rate/verify-rate-key
get /rate/verify-rate-key
Verify Rate Key.
# Get Supported Assets
Source: https://docs.getpartna.com/api-reference/endpoint/v3/supported/get-supported-assets
get /supported/assets
Get a list of supported assets including symbols, contract details, withdrawal constraints, and transaction explorer links.
# Convert Currency
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/convert-currency
post /convert
Convert between currencies.
# Create Transfer
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/create-transfer
post /transfer
Create transfer.
# Get Single Transaction
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/get-single-transaction
get /transfer/details
Get single transaction by reference.
# Get Transfer
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/get-transfer
get /transfer
Get transactions.
# Initiate Transfer
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/initiate-transfer
post /transfer/initiate
Initiate transfer for KES or GHS. This is not required for NGN.
# Withdraw
Source: https://docs.getpartna.com/api-reference/endpoint/v3/transfer/withdraw
post /transfer/withdraw
Initiate withdrawal.
# Create User
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/create-user
post /user/activate
Create and activate user accounts.
# Delete API Key
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/delete-api-key
delete /user/apikey
Delete user API key.
# Delete Webhook URL
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/delete-webhook-url
delete /user/webhook
Delete user webhook URL.
# Generate API Key
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/generate-api-key
post /user/apikey
Generate user API keys.
# Get API Key scopes
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-api-key-scopes
get /user/apikey-scope
Get the valid scopes for API jey generation.
# Get API Keys
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-api-keys
get /user/apikeys
Get user API keys.
# Get Balances
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-balances
get /user/balances
Get merchant and customer balances.
# Get Customer's Account Profile
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-customers-account-profile
get /user/profile
Get customer's account profile.
# Get Registration, Deposit and Fee Settings
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-registration-deposit-and-fee-settings
get /user/settings
Get preferred registration type, fee settings, deposit account, and crypto payment source.
# Get Webhook URL
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/get-webhook-url
get /user/webhook
Get user webhook URL.
# Invalidate API Key
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/invalidate-api-key
post /user/invalidate-apikey
Invalidate user API key.
# Registration, Deposit and Fee Settings
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/registration-deposit-and-fee-settings
put /user/settings
Update preferred registration type, fee settings, deposit account, and crypto payment source.
# Update User
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/update-user
put /user
Update user endpoint ID and callback URL.
# Update Webhook URL
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/update-webhook-url
post /user/webhook
Update user webhook URL.
# Used to delete an admin with privileges
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/used-to-delete-an-admin-with-privileges
delete /user/admin
Used to delete an admin with privileges.
# Used to get all active admins with privileges
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/used-to-get-all-active-admins-with-privileges
get /user/admin
Used to get all active admins with privileges.
# Used to give admin privileges.
Source: https://docs.getpartna.com/api-reference/endpoint/v3/user/used-to-give-admin-privileges
post /user/admin
The first value of the roles array should be 'admin' followed by other permissions (view, operation, settings)
# API Reference
Source: https://docs.getpartna.com/v4/api-reference/overview
Complete reference for all Partna API endpoints
This is the complete reference for the Partna v4 API. All endpoints use the same base URL and authentication pattern.
## Base URLs
| Environment | Base URL |
| ----------- | -------------------------------------- |
| Staging | `https://staging-api.getpartna.com/v4` |
| Production | `https://api.getpartna.com/v4` |
## Authentication
All requests require two headers:
```text theme={null}
x-api-key: YOUR_API_KEY
x-api-user: YOUR_USERNAME
```
See [Authentication](/v4/documentation/authentication) for details.
## Request format
All `POST` and `PUT` requests accept JSON bodies. Set the `Content-Type: application/json` header.
## Response format
All responses return JSON with this structure:
```json theme={null}
{
"data": { },
"message": "success"
}
```
Error responses include a descriptive message:
```json theme={null}
{
"message": "Description of what went wrong"
}
```
## Endpoint groups
**Account**: Create and manage user accounts, bank accounts, and profiles.
**Ramp**: Initiate onramp (fiat-to-crypto) and offramp (crypto-to-fiat) transactions. This is the core of the Partna API.
**Rate**: Fetch conversion rates and create/verify rate keys for locking prices.
**Transfer**: Create transfers, initiate payouts, convert currencies, and withdraw balances.
**KYC**: Identity verification flows including BVN verification, OTP confirmation, and bank account resolution.
**Bank**: List supported banks by currency.
**Discount**: Fetch current volume-based discount tiers.
**Supported**: List supported crypto assets and networks.
**User**: Manage merchant settings, API keys, webhook URLs, balances, and admin access.
**MFA**: Enable and manage multi-factor authentication for accounts.
**Mock (Staging Only)**: Simulate deposits for testing.
# Authentication
Source: https://docs.getpartna.com/v4/documentation/authentication
Authenticate your API requests with API keys
Every request to the Partna API must include two headers: `x-api-key` and `x-api-user`.
## Headers
| Header | Description |
| ------------ | ------------------------------------------------------------------------- |
| `x-api-key` | Your API key. Provided during onboarding for both staging and production. |
| `x-api-user` | Your merchant username. Provided during onboarding. |
## Example request
```bash cURL theme={null}
curl --request GET \
--url https://api.getpartna.com/v4/account \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_MERCHANT_USERNAME'
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.getpartna.com/v4/account",
headers={
"x-api-key": "YOUR_API_KEY",
"x-api-user": "YOUR_MERCHANT_USERNAME"
}
)
print(response.json())
```
```javascript Node.js theme={null}
const response = await fetch("https://api.getpartna.com/v4/account", {
headers: {
"x-api-key": "YOUR_API_KEY",
"x-api-user": "YOUR_MERCHANT_USERNAME"
}
});
const data = await response.json();
console.log(data);
```
## Managing API keys
You can generate, rotate, and revoke API keys programmatically:
| Action | Endpoint |
| ------------------ | ----------------------------------------------------------------------------------- |
| Generate a new key | [POST /user/api-key](/api-reference/endpoint/v3/user/generate-api-key) |
| List all keys | [GET /user/api-keys](/api-reference/endpoint/v3/user/get-api-keys) |
| View key scopes | [GET /user/api-key/scopes](/api-reference/endpoint/v3/user/get-api-key-scopes) |
| Invalidate a key | [POST /user/api-key/invalidate](/api-reference/endpoint/v3/user/invalidate-api-key) |
| Delete a key | [DELETE /user/api-key](/api-reference/endpoint/v3/user/delete-api-key) |
## Security best practices
**Never expose your API key in client-side code.** All API calls should be made from your backend server. The `x-api-key` grants full access to your merchant account.
**Use separate keys for staging and production.** Your staging credentials only work against the staging environment and vice versa.
**Rotate keys periodically.** Use the Generate API Key endpoint to create a new key, update your integration, then invalidate the old key.
## Getting credentials
If you do not have credentials yet, [request access here](https://forms.getpartna.com/get-started). The Partna team will contact you to complete onboarding and set up your merchant account. Once your account is active, generate your API keys from the dashboard:
* Staging: [staging-dashboard.getpartna.com](https://staging-dashboard.getpartna.com/settings?tab=api-webhooks)
* Production: [dashboard.getpartna.com](https://dashboard.getpartna.com/settings?tab=api-webhooks)
# Errors
Source: https://docs.getpartna.com/v4/documentation/core-concepts/errors
HTTP status codes and error response format
The Partna API uses standard HTTP status codes. All error responses include a JSON body with a `message` field describing what went wrong.
## Error response format
```json theme={null}
{
"message": "A human-readable description of the error"
}
```
Some error responses also include a `data` field with additional context.
## HTTP status codes
| Code | Meaning | When you will see it |
| ----- | --------------------- | -------------------------------------------------------------------------------------------------- |
| `200` | OK | Request succeeded |
| `201` | Created | Resource created successfully |
| `400` | Bad Request | Missing required fields, invalid parameter values, or malformed request body |
| `401` | Unauthorized | Missing or invalid `x-api-key` or `x-api-user` headers |
| `403` | Forbidden | Your API key does not have permission for this action |
| `404` | Not Found | The requested resource does not exist |
| `409` | Conflict | A conflicting request is already in progress (e.g., a pending ramp request for the same user) |
| `422` | Unprocessable Entity | The request is well-formed but cannot be processed (e.g., insufficient balance, KYC not completed) |
| `429` | Too Many Requests | Rate limit exceeded. Back off and retry. |
| `500` | Internal Server Error | Something went wrong on Partna's side. Contact support if this persists. |
| `503` | Service Unavailable | The service is temporarily unavailable (e.g., rate service down). Retry after a short delay. |
## Common errors and solutions
### Authentication errors (401)
**"Unauthorized"** or **"Invalid API key"**: Confirm that both `x-api-key` and `x-api-user` headers are present and correct. Staging and production credentials are not interchangeable.
### Ramp request errors (400, 409)
**"User already has a pending ramp request"**: Each user can only have one active ramp request at a time. Either wait for it to complete or set `cancelPendingRampRequest: true` in your next ramp request to cancel the existing one and create a new one.
**"Invalid rate key"**: The rate key has expired or is malformed. Fetch a fresh rate using [Get Rate](/api-reference/endpoint/v3/rate/get-rate) and use the new key.
### KYC errors (422)
**"KYC not completed"**: The user account has not passed identity verification. Complete the KYC flow before initiating transactions. See [Accounts and KYC](/v4/documentation/guides/accounts-and-kyc).
### Balance errors (422)
**"Insufficient balance"**: Your merchant payout balance does not have enough funds to cover the withdrawal. Fund your account before retrying.
## Retry guidance
For `429` and `503` responses, use exponential backoff starting at 1 second. Do not retry `400`, `401`, `403`, or `422` errors without fixing the underlying issue first. `500` errors can be retried, but if they persist, contact [dev@getpartna.com](mailto:dev@getpartna.com).
# Rates and Fees
Source: https://docs.getpartna.com/v4/documentation/core-concepts/rates-and-fees
Conversion rates, rate keys, and volume-based discounts
Partna provides real-time conversion rates between supported currencies. Rates are fetched via the API and locked using rate keys before initiating a transaction.
## Fetching rates
Use the [Get Rate](/api-reference/endpoint/v3/rate/get-rate) endpoint to fetch current conversion rates.
```bash theme={null}
curl --request GET \
--url 'https://api.getpartna.com/v4/rate?fromCurrency=NGN&toCurrency=USDT&fromAmount=100000' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
The response includes a `key` field for each rate pair:
```json theme={null}
{
"data": {
"rate": {
"USDT_to_NGN": {
"fromAmount": 1,
"fromCurrency": "USDT",
"key": "1640-USDT_to_NGN-a1b2c3d4-1755873777",
"rate": 1640,
"toAmount": 1640,
"toCurrency": "NGN"
}
}
},
"message": "success"
}
```
## Rate keys
A rate key locks in a quoted conversion rate for a limited time. When you initiate an onramp or offramp request, pass the `rateKey` from the rate response to guarantee the quoted price.
You can also create a custom rate key using the [Create Ratekey](/api-reference/endpoint/v3/rate/create-a-custom-ratekey-for-conversion) endpoint if you need to apply a custom spread or margin. Verify a rate key is still valid using [Verify Ratekey](/api-reference/endpoint/v3/rate/verify-rate-key).
Rate keys expire. If a rate key expires before the transaction completes, the `expireAction` parameter on the ramp request controls what happens:
| expireAction | Behavior |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| `useCurrentRate` | The transaction proceeds at whatever the current rate is at settlement time. This is the default. |
| `deposit` | The funds are deposited to the user's balance instead of completing the conversion. |
## Fee bearer
When creating transactions, the `feeBearer` parameter controls who pays the transaction fee:
| Value | Behavior |
| ---------- | ---------------------------------------------------- |
| `merchant` | Fees are deducted from your merchant balance |
| `client` | Fees are deducted from the customer's payment amount |
Configure the default fee bearer in your merchant settings via the [Update Settings](/api-reference/endpoint/v3/user/registration-deposit-and-fee-settings) endpoint.
# Testing
Source: https://docs.getpartna.com/v4/documentation/core-concepts/testing-1
Test your integration with mock transactions in staging
You can use [the mock UI](https://staging-dashboard.getpartna.com/mock/deposit) on the staging dashboard to execute test transactions with a few clicks. The staging environment also includes mock API endpoints for more flexibility. Use them to test your integration end-to-end without moving real funds.
## Mock endpoints
### Mock Deposit
Simulates a crypto deposit to a user's account. Use this to test webhook delivery for `Deposit` events and to fund test accounts for subsequent operations.
```bash theme={null}
curl --request POST \
--url https://staging-api.getpartna.com/v4/mock/deposit \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"amount": 100,
"currency": "USDT"
}'
```
See the full reference: [Mock Deposit](/api-reference/endpoint/v3/mock/mock-deposit)
### Mock Fiat Deposit
Simulates a fiat (local currency) deposit. Use this to test the collect flow and onramp scenarios where a user pays in fiat.
```bash theme={null}
curl --request POST \
--url https://staging-api.getpartna.com/v4/mock/fiat-deposit \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"amount": 50000,
"currency": "NGN"
}'
```
See the full reference: [Mock Fiat Deposit](/api-reference/mock/mock-fiat-deposit)
You can also run these from the staging dashboard UI: [Mock Deposits](https://staging-dashboard.getpartna.com/mock/deposit).
## Testing workflow
A typical testing workflow looks like this:
Use [Create Account](/api-reference/account/create-account) to set up a test user. In staging, KYC can be completed with test data.
Run through the [KYC flow](/guides/accounts-and-kyc) using test identity data. In staging, you can use any 11-digit BVN, `08030013843` for phone verification, and `123456` as the OTP.
Call [Get Rate](/api-reference/rate/get-rate) to get a conversion rate and rate key.
Use [Onramp and Offramp](/api-reference/ramp/onramp-and-offramp) to create a test transaction.
For onramp: call Mock Fiat Deposit to simulate the user's local currency payment. For offramp: call Mock Deposit to simulate an incoming crypto payment.
Confirm your webhook endpoint received the expected events (`Deposit`, `Convert`, `Transfer`, etc.) with valid signatures.
## Webhook testing tips
If you do not have a publicly accessible server during development, use a tunneling tool like [ngrok](https://ngrok.com) or [webhook.site](https://webhook.site) to expose your local endpoint:
```bash theme={null}
ngrok http 3000
```
Then set your webhook URL to the forwarding address (e.g., `https://abc123.ngrok.io/webhook`) via the [Update Webhook URL](/api-reference/user/update-webhook-url) endpoint. If you just want to inspect payloads without writing code, webhook.site gives you a unique URL that logs every request it receives.
## Going to production
When your staging integration is working correctly:
1. Swap the base URL from `https://staging-api.getpartna.com/v4` to `https://api.getpartna.com/v4`
2. Replace staging credentials with your production `x-api-key` and `x-api-user`
3. Update your webhook signature verification to use the production public key
4. Update your webhook URL to your production endpoint
No other code changes are required. The API behaves identically in both environments.
# Webhooks
Source: https://docs.getpartna.com/v4/documentation/core-concepts/webhooks
Receive real-time notifications for transaction and verification events
Webhooks notify your application in real time when transaction events occur. Configure a webhook URL to receive updates for deposits, conversions, transfers, onramp requests, and offramp requests.
## Setup
Configure your webhook URL through the [dashboard](/v4/documentation/environments#dashboard) or programmatically via the [Update Webhook URL](/api-reference/endpoint/v3/user/update-webhook-url) endpoint.
Your endpoint must be a publicly accessible `POST` URL that returns a `200` status code immediately. Process events asynchronously after acknowledging receipt.
## Receiving events
Every webhook payload includes three fields:
| Field | Type | Description |
| ----------- | ------ | ------------------------------------------------------- |
| `event` | string | The event type (e.g., `Deposit`, `Convert`, `Transfer`) |
| `data` | object | Event-specific payload |
| `signature` | string | RSA signature for verification |
## Supported events
| Event | Description |
| ---------------------- | -------------------------------------------------- |
| `Deposit` | A fiat or crypto deposit was received |
| `Convert` | A currency conversion completed |
| `Transfer` | A transfer (payout/withdrawal) status changed |
| `Onramp` | An onramp (fiat-to-crypto) request status changed |
| `Withdrawal` | A withdrawal was processed |
| `Offramp` | An offramp (crypto-to-fiat) request status changed |
| `verification.success` | A user successfully completed KYC verification |
| `verification.failed` | A user failed KYC verification (includes reason) |
### Sample events
```json Deposit theme={null}
{
"event": "Deposit",
"data": {
"amount": 5001,
"toUser": "chng",
"currency": "NGN",
"toAccount": "chng",
"status": "completed",
"sessionId": "3c58db9",
"transactionMemo": "Mock Deposit"
},
"signature": "m9STu6pcsviYMfgu5JBjuR"
}
```
```json Convert theme={null}
{
"event": "convert",
"data": {
"transactionReference": "a1fe4aa5679d7",
"fromUser": "chng",
"toAccount": "chng",
"fromAmount": 5,
"toAmount": 6000.05,
"fromCurrency": "USD",
"toCurrency": "NGN"
},
"signature": "gTZdvoadZscP3g=="
}
```
```json Transfer theme={null}
{
"event": "Transfer",
"data": {
"transactionReference": "a16db1274783",
"amount": 1,
"fromUser": "chngr",
"toUser": "chngr",
"currency": "USD",
"fromAccount": "chngr",
"toAccount": "rhmn",
"status": "completed"
},
"signature": "C73lZKC80Cg=="
}
```
```json Onramp theme={null}
{
"event": "Onramp",
"data": {
"transactionReference": "a1e681b09cc7ccea",
"toUser": "chngr",
"toAccount": "chngr",
"status": "completed",
"sessionId": "52567c9fb6864a8c94549675c70cc9a8",
"fromAmount": 149775,
"toAmount": 109.99175768162324,
"fromCurrency": "NGN",
"toCurrency": "USDT",
"transactionMemo": "Mock Deposit"
},
"signature": "Wh+Jqnxc2Iw=="
}
```
```json Withdrawal theme={null}
{
"event": "Withdrawal",
"data": {
"transactionReference": "a1e687ccea",
"amount": 109.9917,
"fromUser": "chang",
"currency": "USDT",
"network": "tron",
"fromAccount": "chang",
"toAddress": "TSqRgHDE1Nypqo1LUp6Q",
"status": "processing",
"transactionHash": "3fa07c748260f469896bf"
},
"signature": "dC8YIklLc08uiGxA=="
}
```
```json Offramp theme={null}
{
"event": "Offramp",
"data": {
"transactionReference": "691af31ba66789",
"toUser": "chng",
"toAccount": "chng",
"status": "completed",
"transactionHash": "99d7c7b5f8f3447d0cc20279f8bab2f827558c5b76",
"fromAmount": 84.2,
"toAmount": 99879.3927,
"fromCurrency": "USDT",
"toCurrency": "NGN"
},
"signature": "faUgOe8SYUfBlvRWsDxg=="
}
```
## Verifying signatures
Every webhook includes an RSA signature. Always verify the signature before processing an event to confirm it came from Partna.
The signature is generated by signing the JSON-encoded `data` field with Partna's RSA private key using SHA-256 with PSS padding. Verify it against the corresponding public key for your environment.
```javascript Node.js theme={null}
import crypto from "crypto";
const SAMPLE_WEBHOOK_REQUEST = `{"event":"Convert","data":{"transactionReference":"a1632e4ea53f2802846cf7797d29bfba","fromUser":"eltnegbiz2","toAccount":"eltnegbiz2","timestamp":1775132847,"fromAmount":1,"toAmount":1000.5,"fromCurrency":"USD","toCurrency":"NGN"},"signature":"ISPOtoKOJRSuT0rKoz0P7JSPSna6UztGOrkKJKWSn62SqUobtaCYURVbhE6A8eIt82qJ9+32XlK1GJZkA5A64MYuDBLF9MdlHvfuNDkPRTof4RV+8UdlSifmBqAU2oYtvA6z4aFHUdiRcfpPNSdkRmLARwIbBxv9Zg6GHFV5HKqybMNt0QaCQskUWVcWOnCEf9PpKV1cKhJWWchYNpiCJgcV8N/vUgOFFVymhkXzbfZADuLukEwScJ751Ll97wWpLwZ1s+1DoBNQ5cY32ft80c3fwnO1NpAW/n22Bpf82rdLP1JctaJUV/3yuorMh+qNaKaXocAMpn6Vkxu8NdyNdQ=="}`;
const PUBLIC_KEY = `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAv2ipgHLFFHgGHr9VpsPN8V1HIbCrlmTZRU/CYSDaoVX+xerJOMGX
qmwgQMQH5T81VaMw4rtIA8tT4DkJgjb+7G0x4CGK1OPdlvhEGP2mOFy02onkEnMv
uN3glVc4YKLvWDTG0KT7q9mARBIkO2Nrwy6IVHAl9pMXMJTRS22c0cIbuRmkYsGZ
trylUv50knbRSgy5EA6523+j3PPJB4TgsigGSJxJGuksaxnDQGRE558xnyw/0gJm
mAIdbxboQTGMqod/My/kAssRkUNu1QtqrsdhZmGYHS+pIPJSaxqHEy8eiTahoqqq
8KgNUfQfwduG+Kc4f/t5JHetSt1dgulmswIDAQAB
-----END RSA PUBLIC KEY-----`;
function verifySignature(data: string, signature: string) {
return crypto.verify(
"sha256",
Buffer.from(JSON.stringify(data), "utf8"),
{
key: PUBLIC_KEY,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
},
Buffer.from(signature, "base64"),
);
}
function run() {
const { event, data, signature } = JSON.parse(SAMPLE_WEBHOOK_REQUEST);
const isValid = verifySignature(data, signature);
if (isValid) {
console.log("Signature is valid. Processing event:", event);
// Process the event data as needed
} else {
console.error("Invalid signature. Possible tampering detected.");
}
}
run();
```
```python Python theme={null}
# install dependencies with `pip install cryptography`
import base64
import json
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
SAMPLE_WEBHOOK_REQUEST = b'{"event":"Convert","data":{"transactionReference":"a1632e4ea53f2802846cf7797d29bfba","fromUser":"eltnegbiz2","toAccount":"eltnegbiz2","timestamp":1775132847,"fromAmount":1,"toAmount":1000.5,"fromCurrency":"USD","toCurrency":"NGN"},"signature":"ISPOtoKOJRSuT0rKoz0P7JSPSna6UztGOrkKJKWSn62SqUobtaCYURVbhE6A8eIt82qJ9+32XlK1GJZkA5A64MYuDBLF9MdlHvfuNDkPRTof4RV+8UdlSifmBqAU2oYtvA6z4aFHUdiRcfpPNSdkRmLARwIbBxv9Zg6GHFV5HKqybMNt0QaCQskUWVcWOnCEf9PpKV1cKhJWWchYNpiCJgcV8N/vUgOFFVymhkXzbfZADuLukEwScJ751Ll97wWpLwZ1s+1DoBNQ5cY32ft80c3fwnO1NpAW/n22Bpf82rdLP1JctaJUV/3yuorMh+qNaKaXocAMpn6Vkxu8NdyNdQ=="}'
PUBLIC_KEY_PEM = b"""-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAv2ipgHLFFHgGHr9VpsPN8V1HIbCrlmTZRU/CYSDaoVX+xerJOMGX
qmwgQMQH5T81VaMw4rtIA8tT4DkJgjb+7G0x4CGK1OPdlvhEGP2mOFy02onkEnMv
uN3glVc4YKLvWDTG0KT7q9mARBIkO2Nrwy6IVHAl9pMXMJTRS22c0cIbuRmkYsGZ
trylUv50knbRSgy5EA6523+j3PPJB4TgsigGSJxJGuksaxnDQGRE558xnyw/0gJm
mAIdbxboQTGMqod/My/kAssRkUNu1QtqrsdhZmGYHS+pIPJSaxqHEy8eiTahoqqq
8KgNUfQfwduG+Kc4f/t5JHetSt1dgulmswIDAQAB
-----END RSA PUBLIC KEY-----"""
def verify_signature(payload: bytes, signature: str) -> bool:
public_key = serialization.load_pem_public_key(PUBLIC_KEY_PEM)
sig_bytes = base64.b64decode(signature)
try:
public_key.verify(
sig_bytes,
payload,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256(),
)
return True
except Exception:
return False
def main():
json_data = json.loads(SAMPLE_WEBHOOK_REQUEST)
event = json_data["event"]
signature = json_data["signature"]
# extract data bytes
payload = SAMPLE_WEBHOOK_REQUEST[
SAMPLE_WEBHOOK_REQUEST.index(b'"data":') + 7
:
SAMPLE_WEBHOOK_REQUEST.index(b"}") + 1
]
if verify_signature(payload, signature):
print(f"Signature valid for event: {event}")
# Process the event data as needed
else:
print("Invalid signature")
if __name__ == "__main__":
main()
```
```go Go theme={null}
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
)
var (
SAMPLE_WEBHOOK_REQUEST = `{"event":"Convert","data":{"transactionReference":"a1632e4ea53f2802846cf7797d29bfba","fromUser":"eltnegbiz2","toAccount":"eltnegbiz2","timestamp":1775132847,"fromAmount":1,"toAmount":1000.5,"fromCurrency":"USD","toCurrency":"NGN"},"signature":"ISPOtoKOJRSuT0rKoz0P7JSPSna6UztGOrkKJKWSn62SqUobtaCYURVbhE6A8eIt82qJ9+32XlK1GJZkA5A64MYuDBLF9MdlHvfuNDkPRTof4RV+8UdlSifmBqAU2oYtvA6z4aFHUdiRcfpPNSdkRmLARwIbBxv9Zg6GHFV5HKqybMNt0QaCQskUWVcWOnCEf9PpKV1cKhJWWchYNpiCJgcV8N/vUgOFFVymhkXzbfZADuLukEwScJ751Ll97wWpLwZ1s+1DoBNQ5cY32ft80c3fwnO1NpAW/n22Bpf82rdLP1JctaJUV/3yuorMh+qNaKaXocAMpn6Vkxu8NdyNdQ=="}`
PUBLIC_KEY_PEM = `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAv2ipgHLFFHgGHr9VpsPN8V1HIbCrlmTZRU/CYSDaoVX+xerJOMGX
qmwgQMQH5T81VaMw4rtIA8tT4DkJgjb+7G0x4CGK1OPdlvhEGP2mOFy02onkEnMv
uN3glVc4YKLvWDTG0KT7q9mARBIkO2Nrwy6IVHAl9pMXMJTRS22c0cIbuRmkYsGZ
trylUv50knbRSgy5EA6523+j3PPJB4TgsigGSJxJGuksaxnDQGRE558xnyw/0gJm
mAIdbxboQTGMqod/My/kAssRkUNu1QtqrsdhZmGYHS+pIPJSaxqHEy8eiTahoqqq
8KgNUfQfwduG+Kc4f/t5JHetSt1dgulmswIDAQAB
-----END RSA PUBLIC KEY-----`
)
func VerifyData(data []byte, signature string) bool {
sig, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
fmt.Println(err)
return false
}
block, _ := pem.Decode([]byte(PUBLIC_KEY_PEM))
if block == nil {
fmt.Println("failed to parse PEM block containing the public key")
return false
}
pubkey, _ := x509.ParsePKCS1PublicKey(block.Bytes)
msgHash := sha256.New()
msgHash.Write(data)
msgHashSum := msgHash.Sum(nil)
err = rsa.VerifyPSS(pubkey, crypto.SHA256, msgHashSum, sig, nil)
return err == nil
}
func main() {
var data struct {
Event string `json:"event"`
Data json.RawMessage `json:"data"`
Signature string `json:"signature"`
}
json.Unmarshal([]byte(SAMPLE_WEBHOOK_REQUEST), &data)
isValid := VerifyData(data.Data, data.Signature)
if isValid {
fmt.Println("Signature is valid")
} else {
fmt.Println("Signature is invalid")
}
}
```
## Public keys
Use the correct public key for your environment.
### Staging
```text theme={null}
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAv2ipgHLFFHgGHr9VpsPN8V1HIbCrlmTZRU/CYSDaoVX+xerJOMGX
qmwgQMQH5T81VaMw4rtIA8tT4DkJgjb+7G0x4CGK1OPdlvhEGP2mOFy02onkEnMv
uN3glVc4YKLvWDTG0KT7q9mARBIkO2Nrwy6IVHAl9pMXMJTRS22c0cIbuRmkYsGZ
trylUv50knbRSgy5EA6523+j3PPJB4TgsigGSJxJGuksaxnDQGRE558xnyw/0gJm
mAIdbxboQTGMqod/My/kAssRkUNu1QtqrsdhZmGYHS+pIPJSaxqHEy8eiTahoqqq
8KgNUfQfwduG+Kc4f/t5JHetSt1dgulmswIDAQAB
-----END RSA PUBLIC KEY-----
```
### Production
```text theme={null}
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAoGmOqVUxIGq92P8J5KqwJbwucsNeUwfS76saj0UPgT1IsK/mvceR
Ges7dsgE4EVx7Wsd5PPNeAfaNt8d0plBLhHRW64WFyv6jYcYp8eVdHUxWLA6p5gZ
9rGrZiwqKqunppTPlV04gdDC32rAbpAR3IMYmMJLuPy63Oumszl4qk69A1o60Son
r0KYBaRK7aQsFT9IFexicDUhrF1SohaNH/msTdvJb0SwSGiV92EhmmC2R8CL83/B
S8QC5c9PWtZ4a26CRLHe0IfaGGz8ClhlO8IFxz0cNrpoRa4JRsffezQ+RMQqCxhc
igC7wHfqZr5BtziQOYJjUknVzsd81HEYlwIDAQAB
-----END RSA PUBLIC KEY-----
```
## Best practices
**Always verify signatures.** Never process a webhook event without verifying the signature first. This prevents spoofed events from affecting your system.
**Return 200 immediately.** Acknowledge receipt before processing. If your endpoint takes too long to respond, the delivery may be retried.
**Handle duplicates.** Use the `sessionId` or `rampReference` in the event data as an idempotency key. The same event may be delivered more than once.
**Log raw payloads.** Store the raw webhook body for debugging. If something goes wrong, the raw payload is essential for troubleshooting.
# Environments
Source: https://docs.getpartna.com/v4/documentation/environments
Staging and production base URLs
Partna provides separate staging and production environments. Build and test in staging before deploying to production.
## Base URLs
| Environment | Base URL |
| -------------- | -------------------------------------- |
| **Staging** | `https://staging-api.getpartna.com/v4` |
| **Production** | `https://api.getpartna.com/v4` |
All endpoint paths in this documentation are relative to these base URLs. For example, the Get Rate endpoint is:
* Staging: `https://staging-api.getpartna.com/v4/rate`
* Production: `https://api.getpartna.com/v4/rate`
## Widget URLs
Use these base URLs for widget integrations:
| Environment | Widget Base URL |
| -------------- | ------------------------------------ |
| **Staging** | `https://staging-pay.getpartna.com/` |
| **Production** | `https://pay.getpartna.com/` |
## Staging environment
The staging environment mirrors production behavior but uses test data. Key differences:
* **No real money moves.** Transactions are simulated.
* **Mock endpoints available.** Use [Mock Deposit](/api-reference/endpoint/v3/mock/mock-deposit) and [Mock Fiat Deposit](/api-reference/endpoint/v3/mock/mock-fiat-deposit) to simulate incoming payments and test your webhook handling.
* **Separate credentials.** Your staging `x-api-key` and `x-api-user` are different from production. They are provided during onboarding.
Use staging to validate your integration end-to-end before switching to production credentials.
## Production environment
The production environment processes real transactions with real funds. Switch to production by changing the base URL and credentials. No other code changes are required.
## Dashboard
Manage your merchant settings, view transactions, and configure webhooks:
| Environment | Dashboard |
| :------------- | :-------------------------------------------------------------------------- |
| **Staging** | [staging-dashboard.getpartna.com](https://staging-dashboard.getpartna.com/) |
| **Production** | [dashboard.getpartna.com](https://dashboard.getpartna.com/) |
# Accounts and KYC
Source: https://docs.getpartna.com/v4/documentation/guides/accounts-and-kyc
Create user accounts and verify identity
Every end user who transacts through Partna needs an account and must complete identity verification (KYC). This is a regulatory requirement for all transactions. Users only need to verify once.
## Creating an account
Use [Create Account](/api-reference/endpoint/v3/account/create-account) to register a new user:
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/account \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"email": "user@example.com",
"accountName": "Jane Doe"
}'
```
You can also use [Register Account](/api-reference/endpoint/v3/account/register-account) if your merchant registration type is set to "open", which creates a pending registration that the user can complete later.
## KYC verification flow
Identity verification in Nigeria uses BVN (Bank Verification Number). The flow depends on the user's country and the available verification methods.
Call [Initiate KYC](/api-reference/endpoint/v3/kyc/initiate-bvn-kyc) with the user's identity information (e.g., BVN for Nigeria). This triggers an OTP to be sent to the phone number associated with the user's identity. In staging, you can use any 11-digit number as the BVN to simulate a successful verification.
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/kyc/initiate \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"bvn": "12345678901",
"accountName": "user-account-name"
}'
```
After OTP verification, confirm the user's phone number using [Confirm Phone](/api-reference/endpoint/v3/kyc/confirm-phone). In staging, use `08030013843` when asked to verify the user's phone number.
Call [Supported Verification Methods](/api-reference/endpoint/v3/kyc/supported-verification-methods) to see which identity verification methods are available for a given country.
```bash theme={null}
curl --request GET \
--url 'https://api.getpartna.com/v4/kyc/verification-methods?country=NG' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
Once the user receives the OTP, submit it using [Confirm KYC OTP](/api-reference/endpoint/v3/kyc/confirm-kyc-otp). In staging, use `123456` as the OTP to simulate a successful verification.
```bash theme={null}
curl --request PUT \
--url https://api.getpartna.com/v4/kyc/confirm-otp \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"otp": "123456",
"accountName": "user-account-name"
}'
```
In some cases, the user may need to select how they receive the OTP (e.g., SMS or phone call).
After successful verification, you will receive a `verification.success` webhook event or a `verification.failed` event with reason if verification failed. If successful, the user can now transact.
## Merchant-managed KYC
If you have already verified your users' identities through your own KYC process, you can use [Merchant KYC Manually](/api-reference/endpoint/v3/kyc/merchant-kyc-users-manually) to mark a user as verified without going through Partna's KYC flow. This requires approval from the Partna team during onboarding.
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/kyc/merchant-kyc \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"accountName": "user-account-name",
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-01-15"
}'
```
## Resolving bank accounts
Before initiating payouts or offramp transactions, verify the destination bank account using [Resolve Bank](/api-reference/endpoint/v3/kyc/resolve-bank):
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/kyc/resolve-account \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"accountNumber": "0123456789",
"bankCode": "044",
"currency": "NGN"
}'
```
This returns the account holder's name so you can confirm it matches before sending funds.
## Creating bank accounts for users
Use [Create Bank Account](/api-reference/endpoint/v3/account/create-bank-account) to link a verified bank account to a user's Partna account. This is required before the user can receive local currency payouts.
## Account management
| Action | Endpoint |
| ------------------- | ------------------------------------------------------------------------------------- |
| Get account details | [Get Account](/api-reference/endpoint/v3/account/get-account) |
| List accounts | [Get Accounts Details](/api-reference/endpoint/v3/account/get-accounts-details) |
| Get user profile | [Get Account Profile](/api-reference/endpoint/v3/account/get-account-profile) |
| Update email | [Update Account Email](/api-reference/endpoint/v3/account/update-account-email) |
| Check permissions | [Get Account Permissions](/api-reference/endpoint/v3/account/get-account-permissions) |
| Search accounts | [Search Account](/api-reference/endpoint/v3/account/search-account) |
# Collect and Settle
Source: https://docs.getpartna.com/v4/documentation/guides/collect-and-settle
Collect local currency payments and settle in USD or stablecoins
The collect and settle flow lets you accept payments from African customers in local currency and receive settlement in USD or stablecoins. This is the solution for companies that sell services to African customers and need to get their money out efficiently.
## How it works
After receiving your credentials, configure your merchant account:
* **Callback URL**: Your webhook endpoint for payment notifications
* **Fee bearer**: Whether you or your customer absorbs transaction fees
* **Credit currency**: The currency you want to be settled in (e.g., `USD`, `USDT`, `USDC`)
Configure these via the [dashboard](https://dashboard.getpartna.com) or the [Update Settings](/api-reference/endpoint/v3/user/registration-deposit-and-fee-settings) endpoint.
Register each customer using [Create Account](/api-reference/endpoint/v3/account/create-account). This creates a permanent payment account that your customer can deposit to at any time.
Your customer must verify their identity before making payments. See [Accounts and KYC](/v4/documentation/guides/accounts-and-kyc) for the verification flow. This is a one-time step.
Use the onramp flow to collect a payment. Call [Onramp and Offramp](/api-reference/endpoint/v3/ramp/onramp-and-offramp) with `type: "fiatToCrypto"` to create a payment request. The response includes the bank account details where your customer should send their payment.
Alternatively, if you are using the collect API for simpler collection without crypto conversion, you can create a payment using [Create Transfer](/api-reference/endpoint/v3/transfer/create-transfer) or [Initiate Transfer](/api-reference/endpoint/v3/transfer/initiate-transfer).
Your customer transfers the specified local currency amount to the provided bank account via bank transfer.
Partna converts the local currency and settles in your configured credit currency. You receive a webhook notification when the settlement completes.
## Settlement currencies
You can settle in any supported stablecoin (USDT, USDC) or hold balances in local currency. Configure your preferred settlement currency in your merchant settings.
## Checking your balance
Use [Get Balances](/api-reference/endpoint/v3/user/get-balances) to check your merchant account balances across all currencies:
```bash theme={null}
curl --request GET \
--url https://api.getpartna.com/v4/user/balances \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
## Withdrawing your balance
Once funds are settled in your account, use [Withdraw](/api-reference/endpoint/v3/transfer/withdraw) to send funds to an external wallet or bank account.
## Currency conversion
If you hold balances in one currency and need to convert to another, use [Convert Currency](/api-reference/endpoint/v3/transfer/convert-currency) to convert between supported currencies within your account.
## Use case: SaaS company collecting from Nigerian clients
A typical flow for a SaaS company invoicing Nigerian enterprises:
1. Customer is onboarded and KYC'd once
2. For each invoice, create a ramp request specifying the NGN amount owed
3. Share the generated bank account details with your customer
4. Customer pays via standard Nigerian bank transfer
5. You receive USDT or USD settlement within hours (typically same-day)
6. Withdraw to your corporate wallet or bank account as needed
This replaces the traditional process of waiting weeks or months for cross-border wire transfers from Nigeria.
# Offramp
Source: https://docs.getpartna.com/v4/documentation/guides/offramp
Let your users convert crypto to local currency
The offramp flow lets your users send crypto (USDT, USDC, BTC, ETH) and receive local currency (NGN, GHS, KES) in their bank account or mobile money wallet. Partna handles the crypto receipt, conversion, and local currency payout.
If you want full control over the experience, use this API flow. If you would rather redirect users to a Partna payout flow, use the [Offramp Widget](/v4/documentation/guides/offramp-widget) instead.
## How it works
Register your user with Partna using [Create Account](/api-reference/endpoint/v3/account/create-account) if they do not already have one.
Verify the user's bank account details using [Resolve Bank](/api-reference/endpoint/v3/kyc/resolve-bank). This confirms the account number and returns the account holder's name.
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/kyc/resolve-account \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"accountNumber": "0123456789",
"bankCode": "044",
"currency": "NGN"
}'
```
Response:
```json theme={null}
{
"data": {
"accountName": "JANE DOE",
"accountNumber": "0123456789"
},
"message": "success"
}
```
Call [Get Rate](/api-reference/endpoint/v3/rate/get-rate) with `fromCurrency=USDT` and `toCurrency=NGN`. Save the rate `key`.
```bash theme={null}
curl --request GET \
--url 'https://api.getpartna.com/v4/rate?fromCurrency=USDT&toCurrency=NGN&fromAmount=100' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
Call [Onramp and Offramp](/api-reference/endpoint/v3/ramp/onramp-and-offramp) with `type: "cryptoToFiat"`. Include the rate key, the user's bank details, and the crypto amount.
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/ramp \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"type": "cryptoToFiat",
"fromCurrency": "USDT",
"fromNetwork": "tron",
"fromAmount": 100,
"toCurrency": "NGN",
"toNetwork": "naira",
"accountName": "JANE DOE",
"accountNumber": "0123456789",
"bankCode": "044",
"rateKey": "RATE_KEY_FROM_STEP_4",
"rampReference": "your-unique-reference-id"
}'
```
The response includes a crypto wallet address where the user should send their USDT.
The user sends the specified crypto amount to the wallet address returned in step 5.
Once Partna confirms the crypto deposit, the equivalent local currency is sent to the user's bank account. A webhook event is dispatched to your endpoint.
## Key parameters
| Parameter | Description |
| --------------- | --------------------------------------------------------------------------- |
| `type` | Must be `cryptoToFiat` for offramp |
| `fromCurrency` | The crypto being sent (e.g., `USDT`, `USDC`, `BTC`) |
| `fromNetwork` | The blockchain network (e.g., `tron`, `ethereum`, `solana`) |
| `toCurrency` | The local currency for payout (e.g., `NGN`) |
| `toNetwork` | The payout network (e.g., `naira`, `mobileMoney`, `mpesa`) |
| `accountName` | The bank account holder's name (from Resolve Bank) |
| `accountNumber` | The destination bank account number |
| `bankCode` | The bank code (from [Get Banks](/api-reference/endpoint/v3/bank/get-banks)) |
| `rateKey` | The rate key from [Get Rate](/api-reference/endpoint/v3/rate/get-rate) |
| `rampReference` | Your unique reference for this transaction |
## Custom payout amounts
By default, Partna pays the full converted amount to the user's bank account. If you want to pay a different amount (e.g., to build in a margin), you can adjust the payout using the [Withdraw](/api-reference/endpoint/v3/transfer/withdraw) endpoint after the crypto deposit is confirmed. This requires sufficient funds in your payout balance.
## Getting bank codes
Use [Get Banks](/api-reference/endpoint/v3/bank/get-banks) to fetch the list of supported banks and their codes for a given currency:
```bash theme={null}
curl --request GET \
--url 'https://api.getpartna.com/v4/bank?currency=NGN' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
# Offramp Widget
Source: https://docs.getpartna.com/v4/documentation/guides/offramp-widget
Redirect users to Partna's offramp widget
The offramp widget lets you redirect users to a Partna-managed flow where they can send crypto and receive local currency. Use this flow when you want Partna to handle the payout collection interface and transaction instructions.
## Base URLs
| Environment | URL |
| ------------ | -------------------------------------------------- |
| `staging` | `https://staging-pay.getpartna.com/v4/pay/offramp` |
| `production` | `https://pay.getpartna.com/v4/pay/offramp` |
## Query parameters
| Parameter | Required | Description |
| --------------- | -------- | ----------------------------------------------------------------------- |
| `amount` | Yes | Amount the user wants to convert |
| `from_currency` | Yes | Crypto the user is sending, such as `USDT`, `USDC`, `BTC`, or `ETH` |
| `from_network` | Yes | Network the user is sending from, such as `tron`, `celo`, or `ethereum` |
| `to_currency` | Yes | Local payout currency, such as `NGN`, `KES`, or `GHS` |
| `merchant` | Yes | Your Partna merchant username |
| `address` | No | Wallet address the crypto will be sent from |
| `reference` | No | A hex string transaction reference |
## Registration modes
The widget supports two merchant-controlled registration modes:
| Mode | Behavior |
| -------- | ---------------------------------------------------------------------------------- |
| `open` | Users can begin registration through the widget flow and complete onboarding later |
| `closed` | Only users you have already created can continue in the widget flow |
You can manage this setting through [Update Settings](/api-reference/endpoint/v3/user/registration-deposit-and-fee-settings) and inspect the current value with [Get Settings](/api-reference/endpoint/v3/user/get-registration-deposit-and-fee-settings).
If your registration mode is `open`, you can use [Register Account](/api-reference/endpoint/v3/account/register-account) to create a pending registration. If your registration mode is `closed`, create the user up front with [Create Account](/api-reference/endpoint/v3/account/create-account).
The hosted flow collects the user's payout details before finalizing the transaction.
## How it works
Build a URL using the correct environment URL and the required query parameters.
Send the user to the hosted Partna offramp flow from your app or website.
The user enters the required payout details in the hosted flow and transfers the specified crypto amount.
Partna sends `Offramp` webhook events as the transaction progresses. See [Webhooks](/v4/documentation/core-concepts/webhooks) for verification details.
Once the transaction reaches `completed`, the local currency payout has been sent.
## Build the redirect URL
```typescript TypeScript theme={null}
const redirectURL =
environment === "staging"
? "https://staging-pay.getpartna.com/v4/pay/offramp"
: "https://pay.getpartna.com/v4/pay/offramp";
const params = new URLSearchParams();
params.append("from_currency", fromCurrency);
params.append("to_currency", toCurrency);
params.append("from_network", fromNetwork);
params.append("amount", amount);
if (address) {
params.append("address", address);
}
params.append("merchant", merchant);
if (reference) {
params.append("reference", reference);
}
const url = `${redirectURL}?${params.toString()}`;
window.location.href = url;
```
```javascript JavaScript theme={null}
const redirectURL =
environment === "staging"
? "https://staging-pay.getpartna.com/v4/pay/offramp"
: "https://pay.getpartna.com/v4/pay/offramp";
const params = new URLSearchParams({
from_currency: fromCurrency,
to_currency: toCurrency,
from_network: fromNetwork,
amount,
merchant,
});
if (address) {
params.append("address", address);
}
if (reference) {
params.append("reference", reference);
}
window.location.href = `${redirectURL}?${params.toString()}`;
```
## Example redirect URL
```text theme={null}
https://pay.getpartna.com/v4/pay/offramp?amount=0.11&from_currency=CUSD&from_network=celo&to_currency=KES&address=0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d&merchant=your-merchant-username&reference=a13953b727b9f20ab1ab76dfd9abe4ca
```
For staging, use `https://staging-pay.getpartna.com/v4/pay/offramp` instead.
## Transaction lifecycle
Partna emits `Offramp` webhooks with these statuses:
| Status | Meaning |
| ------------ | ------------------------------------------------------------------- |
| `pending` | The offramp request has been created and is awaiting crypto payment |
| `received` | Partna has received the user's crypto transfer |
| `processing` | Conversion or payout is currently being processed |
| `completed` | The local currency payout is complete |
## Sample webhook payloads
### `pending`
```json theme={null}
{
"event": "Offramp",
"data": {
"transactionReference": "a13953b727b9f20ab1ab76dfd9abe4ca",
"status": "pending",
"timestamp": 1773706850,
"fromAmount": 0.11,
"toAmount": 12.93,
"fromCurrency": "CUSD",
"toCurrency": "KES",
"fromNetwork": "celo",
"toNetwork": "kenyanshilling",
"totalFeesInFromCurrency": 0.0089,
"totalFeesInToCurrency": 1.14,
"sendingAddress": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d",
"paymentAddress": "0xc89f5afe4d5c13daeef96164abc8001c1e320718",
"expireAt": 1773710450,
"toExternalAccount": "Safaricom::0726140198::::MOBILE"
},
"signature": "BASE64_SIGNATURE"
}
```
### `received`
```json theme={null}
{
"event": "Offramp",
"data": {
"transactionReference": "a13953b727b9f20ab1ab76dfd9abe4ca",
"status": "received",
"timestamp": 1773706902,
"transactionHash": "0xeefee6c33e572458e6aef553b1572c58084ad59d7f3bcec9654d60810def6d2b",
"fromAmount": 0.11,
"fromCurrency": "CUSD",
"toCurrency": "KES",
"sendingAddress": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d"
},
"signature": "BASE64_SIGNATURE"
}
```
### `processing`
```json theme={null}
{
"event": "Offramp",
"data": {
"transactionReference": "a13953b727b9f20ab1ab76dfd9abe4ca",
"status": "processing",
"timestamp": 1773706921,
"fromAmount": 0.11,
"toAmount": 12.9392,
"fromCurrency": "CUSD",
"toCurrency": "KES",
"fromNetwork": "celo",
"toNetwork": "kenyanshilling",
"sendingAddress": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d",
"expireAt": 1773710450,
"toExternalAccount": "Safaricom::0726140198::::MOBILE"
},
"signature": "BASE64_SIGNATURE"
}
```
### `completed`
```json theme={null}
{
"event": "Offramp",
"data": {
"transactionReference": "a13953b727b9f20ab1ab76dfd9abe4ca",
"status": "completed",
"timestamp": 1773706921,
"sessionId": "DFE48F597F",
"fromAmount": 0.11,
"toAmount": 12.9392,
"fromCurrency": "CUSD",
"toCurrency": "KES",
"fromNetwork": "celo",
"toNetwork": "kenyanshilling",
"sendingAddress": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d",
"toExternalAccount": "Safaricom::0726140198::::MOBILE"
},
"signature": "BASE64_SIGNATURE"
}
```
## When to use the hosted widget
Use the hosted widget when you want:
* A redirect-based offramp experience with less frontend work
* Partna to collect payout details inside the hosted flow
* A fast way to launch crypto-to-fiat payouts
Use the API-based [Offramp Guide](/v4/documentation/guides/offramp) when you need to manage account resolution, payout details, and user experience yourself.
# Onramp
Source: https://docs.getpartna.com/v4/documentation/guides/onramp
Let your users convert local currency to crypto
The onramp flow lets your users pay in local currency (NGN, GHS, KES) and receive crypto (USDT, USDC, BTC, ETH) in their wallet. Partna handles the payment collection, currency conversion, and crypto delivery.
If you want full control over the experience, use this API flow. If you would rather redirect users to a Partna checkout, use the [Onramp Widget](/v4/documentation/guides/onramp-widget) instead.
## How it works
Register your user with Partna using [Create Account](/api-reference/endpoint/v3/account/create-account). This creates a permanent account that can receive payments at any time.
Your user must verify their identity before transacting. See [Accounts and KYC](/v4/documentation/guides/accounts-and-kyc) for the full verification flow. Users only need to verify once.
Call [Get Rate](/api-reference/endpoint/v3/rate/get-rate) with `fromCurrency=NGN` and `toCurrency=USDT` (or your desired fiat/crypto). Save the `key` from the response.
```bash theme={null}
curl --request GET \
--url 'https://api.getpartna.com/v4/rate?fromCurrency=NGN&toCurrency=USDT&fromAmount=10000' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME'
```
Call [Onramp and Offramp](/api-reference/endpoint/v3/ramp/onramp-and-offramp) with `type: "fiatToCrypto"`. Include the rate key, amounts, currency pair, and the user's crypto wallet address.
```bash theme={null}
curl --request POST \
--url https://api.getpartna.com/v4/ramp \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_USERNAME' \
--data '{
"type": "fiatToCrypto",
"fromCurrency": "NGN",
"fromNetwork": "naira",
"fromAmount": 10000,
"toCurrency": "USDT",
"toNetwork": "tron",
"cryptoAddress": "TXyz123...userWalletAddress",
"rateKey": "RATE_KEY_FROM_STEP_3",
"rampReference": "your-unique-reference-id"
}'
```
The response includes a bank account (`accountName`, `accountNumber`, `bankName`) where the user should send their NGN payment.
The user transfers the specified NGN amount to the bank account returned in step 4. This can be done via bank transfer or any supported local payment method.
Once Partna confirms the fiat payment, the equivalent crypto is sent to the user's wallet address. A webhook event is dispatched to your endpoint.
## Example response (step 4)
```json theme={null}
{
"data": {
"accountName": "JANE DOE",
"accountNumber": "9003001002",
"bankName": "Paystack-Titan",
"currentRate": 0.000597221725,
"discountApplied": "0",
"expiryDate": 1755873600,
"feeInFromCurrency": 16.74,
"feeInToCurrency": 0.01,
"fromAmount": 10000,
"fromCurrency": "NGN",
"fromNetwork": "naira",
"rampReference": "f32c3925063447a54905d31305a14da5",
"toAmount": 5.872,
"toCurrency": "USDT",
"toNetwork": "tron",
"totalFeesInFromCurrency": 16.74,
"totalFeesInToCurrency": 0.01
},
"message": "request completed"
}
```
## Key parameters
| Parameter | Description |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `type` | Must be `fiatToCrypto` for onramp |
| `fromCurrency` | The local currency the user is paying in (e.g., `NGN`) |
| `fromNetwork` | The payment network for the local currency (e.g., `naira`, `mobileMoney`, `mpesa`) |
| `toCurrency` | The crypto the user will receive (e.g., `USDT`, `USDC`, `BTC`) |
| `toNetwork` | The blockchain network for delivery (e.g., `tron`, `ethereum`, `solana`) |
| `cryptoAddress` | The user's wallet address to receive crypto |
| `rateKey` | The rate key from [Get Rate](/api-reference/endpoint/v3/rate/get-rate) |
| `rampReference` | Your unique reference for this transaction (use for idempotency) |
| `fromAmount` | The amount in local currency |
| `cancelPendingRampRequest` | Set to `true` to cancel any existing pending request for this user. Default: `false`. |
| `expireAction` | What happens if the rate expires: `useCurrentRate` (default) or `deposit` |
| `rampExpTime` | Custom expiration time in seconds. Default: 1 hour. |
## Handling pending requests
Each user can only have one active ramp request at a time. If you need to create a new request while one is pending, set `cancelPendingRampRequest: true` in your request body.
## Tracking status
Use [Get Ramp Requests](/api-reference/endpoint/v3/ramp/get-ramp-requests) to check the status of a ramp request. You will also receive webhook notifications as the transaction progresses through each stage.
# Onramp Widget
Source: https://docs.getpartna.com/v4/documentation/guides/onramp-widget
Redirect users to Partna's onramp widget
The onramp widget lets you send users to a Partna-managed checkout where they can pay in local currency and receive crypto in their wallet. Use this flow when you want a faster integration without building your own payment UI.
## Base URLs
| Environment | URL |
| ------------ | ------------------------------------------------- |
| `staging` | `https://staging-pay.getpartna.com/v4/pay/onramp` |
| `production` | `https://pay.getpartna.com/v4/pay/onramp` |
## Required query parameters
| Parameter | Required | Description |
| --------------- | -------- | ----------------------------------------------------------------------- |
| `amount` | Yes | Amount the user will pay in local currency |
| `from_currency` | Yes | Local currency the user is paying with, such as `NGN`, `KES`, or `GHS` |
| `to_currency` | Yes | Crypto the user should receive, such as `USDT`, `USDC`, `BTC`, or `ETH` |
| `to_network` | Yes | Network to deliver the crypto on, such as `tron`, `celo`, or `ethereum` |
| `address` | Yes | Wallet address that will receive the crypto |
| `merchant` | Yes | Your Partna merchant username |
| `reference` | No | A hex string transaction reference |
## Registration modes
The widget supports two merchant-controlled registration modes:
| Mode | Behavior |
| -------- | ---------------------------------------------------------------------------------- |
| `open` | Users can begin registration through the widget flow and complete onboarding later |
| `closed` | Only users you have already created can continue in the widget flow |
You can manage this setting through [Update Settings](/api-reference/endpoint/v3/user/registration-deposit-and-fee-settings) and inspect the current value with [Get Settings](/api-reference/endpoint/v3/user/get-registration-deposit-and-fee-settings).
If your registration mode is `open`, you can use [Register Account](/api-reference/endpoint/v3/account/register-account) to create a pending registration. If your registration mode is `closed`, create the user up front with [Create Account](/api-reference/endpoint/v3/account/create-account).
## How it works
Build a URL using the correct environment URL and the required query parameters.
Send the user to the hosted Partna checkout from your app or website.
The user follows the payment instructions in the hosted flow and pays the local currency amount.
Partna sends `Onramp` webhook events as the transaction moves through each state. See [Webhooks](/v4/documentation/core-concepts/webhooks) for signature verification and endpoint setup.
Once the transaction reaches `completed`, the crypto has been sent to the destination wallet address.
## Build the redirect URL
```typescript TypeScript theme={null}
const redirectURL =
environment === "staging"
? "https://staging-pay.getpartna.com/v4/pay/onramp"
: "https://pay.getpartna.com/v4/pay/onramp";
const params = new URLSearchParams();
params.append("from_currency", fromCurrency);
params.append("to_currency", toCurrency);
params.append("to_network", toNetwork);
params.append("amount", amount);
params.append("address", walletAddress);
params.append("merchant", merchant);
if (reference) {
params.append("reference", reference);
}
const url = `${redirectURL}?${params.toString()}`;
window.location.href = url;
```
```javascript JavaScript theme={null}
const redirectURL =
environment === "staging"
? "https://staging-pay.getpartna.com/v4/pay/onramp"
: "https://pay.getpartna.com/v4/pay/onramp";
const params = new URLSearchParams({
from_currency: fromCurrency,
to_currency: toCurrency,
to_network: toNetwork,
amount,
address: walletAddress,
merchant,
});
if (reference) {
params.append("reference", reference);
}
window.location.href = `${redirectURL}?${params.toString()}`;
```
## Example redirect URL
```text theme={null}
https://pay.getpartna.com/v4/pay/onramp?amount=1000&from_currency=KES&to_currency=CUSD&to_network=celo&address=0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d&merchant=your-merchant-username&reference=a152225b9ccd6d28c2b58f8828a81125
```
For staging, use `https://staging-pay.getpartna.com/v4/pay/onramp` instead.
## Transaction lifecycle
Partna emits `Onramp` webhooks with these statuses:
| Status | Meaning |
| ------------ | ---------------------------------------------------------------- |
| `pending` | The onramp request has been created and is awaiting user payment |
| `received` | Partna has received the user's payment |
| `processing` | Conversion or transfer is currently being processed |
| `completed` | The crypto delivery is complete |
## Sample webhook payloads
### `pending`
```json theme={null}
{
"event": "Onramp",
"data": {
"transactionReference": "a152225b9ccd6d28c2b58f8828a81125",
"status": "pending",
"timestamp": 1773760151,
"fromAmount": 14.2,
"toAmount": 0.0954,
"fromCurrency": "KES",
"toCurrency": "CUSD",
"fromNetwork": "kenyanshilling",
"toNetwork": "celo",
"totalFeesInFromCurrency": 1.8,
"totalFeesInToCurrency": 0.0138,
"expireAt": 1773763751,
"toExternalAccount": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d"
},
"signature": "BASE64_SIGNATURE"
}
```
### `received`
```json theme={null}
{
"event": "Onramp",
"data": {
"transactionReference": "a152225b9ccd6d28c2b58f8828a81125",
"status": "received",
"timestamp": 1773760193,
"fromAmount": 14.2,
"toAmount": 0.095407692303476,
"fromCurrency": "KES",
"toCurrency": "CUSD",
"totalFeesInFromCurrency": 1.797000000052,
"totalFeesInToCurrency": 0.013823076922924,
"toExternalAccount": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d"
},
"signature": "BASE64_SIGNATURE"
}
```
### `processing`
```json theme={null}
{
"event": "Onramp",
"data": {
"transactionReference": "a152225b9ccd6d28c2b58f8828a81125",
"status": "processing",
"timestamp": 1773760502,
"transactionHash": "0xc41e313cfb60826d8ccb1803f55fdaaa8d72c269b35fc1a273f7fe3bd7b86831",
"fromAmount": 14.2,
"toAmount": 0.0954,
"fromCurrency": "KES",
"toCurrency": "CUSD",
"fromNetwork": "kenyanshilling",
"toNetwork": "celo",
"expireAt": 1773763751,
"toExternalAccount": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d"
},
"signature": "BASE64_SIGNATURE"
}
```
### `completed`
```json theme={null}
{
"event": "Onramp",
"data": {
"transactionReference": "a152225b9ccd6d28c2b58f8828a81125",
"status": "completed",
"timestamp": 1773760502,
"transactionHash": "0xc41e313cfb60826d8ccb1803f55fdaaa8d72c269b35fc1a273f7fe3bd7b86831",
"fromAmount": 14.2,
"toAmount": 0.0954,
"fromCurrency": "KES",
"toCurrency": "CUSD",
"fromNetwork": "kenyanshilling",
"toNetwork": "celo",
"toExternalAccount": "0x00B6845c6F47C770cE630B96df9BD4A6dA91C65d"
},
"signature": "BASE64_SIGNATURE"
}
```
## When to use the hosted widget
Use the hosted widget when you want:
* A faster integration with less frontend work
* Partna to handle the payment instructions UI
* A simple redirect-based flow from your product
Use the API-based [Onramp Guide](/v4/documentation/guides/onramp) when you need full control over the user experience and payment orchestration.
# Introduction
Source: https://docs.getpartna.com/v4/documentation/introduction
Stablecoin-powered payment infrastructure for Africa
Partna is a payment API that connects African currencies to the global financial system through stablecoins. Collect local currencies, convert between fiat and crypto, and settle in USD or stablecoins.
## What you can build
**Onramp**: Your users pay in local currency (NGN, GHS, KES). They receive USDT, USDC, or other supported crypto in their wallet. The conversion, compliance, and settlement are handled by Partna.
**Offramp**: Your users send crypto. They receive local currency in their bank account or mobile money account. Partna handles the conversion and payout automatically.
**Collect and settle**: Collect payments from African customers in local currency. Settle in USD or stablecoins. No more waiting months for cross-border banking rails to move your money.
**Payout**: Send local currency to bank accounts or mobile money wallets across supported markets. Fund your payout balance with USD, stablecoins, or local currency.
## Supported markets
| Market | Currency | Status |
| ------- | -------- | --------- |
| Nigeria | NGN | Live |
| Kenya | KES | Live |
| Ghana | GHS | Launching |
| Malawi | MWK | Launching |
| Zambia | ZMW | Launching |
## Supported assets
You can retrieve the list of supported assets, by network and by currency, for each environment via the [GET supported assets endpoint](/api-reference/endpoint/v3/supported/get-supported-assets).
## How it works
Partna routes payments through stablecoin rails to bypass the traditional banking bottlenecks that make cross-border settlement slow and expensive. Your customers pay in local currency. You receive USD or stablecoins. Your users never need to touch or understand crypto unless you want them to.
## Trusted by
Minipay (1M+ users, 99% transaction success rates), Cenoa, Grabrfi, Hyperverge, Onramp Money, and Bitoshi. Over 200,000 transactions processed monthly with \$4M+ in monthly volume.
## Next steps
Make your first API call in under 5 minutes.
Get your API credentials and start building.
Let your users convert local currency to crypto.
Let your users convert crypto to local currency.
# Quickstart
Source: https://docs.getpartna.com/v4/documentation/quickstart
Make your first API call in under 5 minutes
This guide walks you through making your first Partna API call. By the end, you will understand the basic request pattern for every endpoint.
## 1. Get your API credentials
Fill out the [onboarding form](https://forms.getpartna.com/get-started). The Partna team will reach out to schedule a brief onboarding call and set up your merchant account.
Once your merchant account is set up, log in to the Partna dashboard, set up 2fa and generate your API keys. You will get separate keys for **staging** and **production**:
* Staging dashboard: [staging-dashboard.getpartna.com](https://staging-dashboard.getpartna.com/settings?tab=api-webhooks)
* Production dashboard: [dashboard.getpartna.com](https://dashboard.getpartna.com/settings?tab=api-webhooks)
Take note of your created **API Key** and **Merchant Username**. You will need to include these as the `x-api-key` and `x-api-user` headers in every authenticated request.
Build and test against the staging environment before deploying to production. Staging supports [mock deposits](https://staging-dashboard.getpartna.com/mock/deposit) and test transactions at no cost.
## 2. Make your first request
Fetch your account details. This is the simplest authenticated request to confirm your credentials work and to see your available balances.
```bash cURL theme={null}
curl --request GET \
--url https://api.getpartna.com/v4/account \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-api-user: YOUR_MERCHANT_USERNAME'
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.getpartna.com/v4/account",
headers={
"x-api-key": "YOUR_API_KEY",
"x-api-user": "YOUR_MERCHANT_USERNAME"
}
)
print(response.json())
```
```javascript Node.js theme={null}
const response = await fetch("https://api.getpartna.com/v4/account", {
headers: {
"x-api-key": "YOUR_API_KEY",
"x-api-user": "YOUR_MERCHANT_USERNAME"
}
});
const data = await response.json();
console.log(data);
```
## 3. Check the response
A successful response returns your account balances across all supported currencies and their underlying assets and asset IDs (wallet addresses). Once you confirm you can fetch your account details, you are ready to start using other Partna API endpoints.
```json theme={null}
{
"data": {
"accounts": [
{
"BNB": {
"accountName": "user1",
"asset": [{ "id": "0x20B3...", "meta": null, "type": "crypto" }],
"balance": 0,
"pendingDebit": 0
},
"BTC": {
"accountName": "user1",
"asset": [{ "id": "tb1q6z...", "meta": null, "type": "crypto" }],
"balance": 0,
"pendingDebit": 0
},
"ETH": {
"accountName": "user1",
"asset": [{ "id": "0x20B3...", "meta": null, "type": "crypto" }],
"balance": 0,
"pendingDebit": 0
},
"GHS": {
"accountName": "user1",
"asset": [{ "id": "", "meta": null, "type": "fiat" }],
"balance": 0,
"pendingDebit": 0
},
"KES": {
"accountName": "user1",
"asset": [{ "id": "", "meta": null, "type": "fiat" }],
"balance": 0,
"pendingDebit": 0
},
"NGN": {
"accountName": "user1",
"asset": [{ "id": "", "meta": null, "type": "fiat" }],
"balance": 0,
"pendingDebit": 0
},
"USD": {
"accountName": "user1",
"asset": [
{ "id": "0x20B3...", "meta": null, "type": "crypto" },
{ "id": "TCx7wx...", "meta": null, "type": "crypto" },
{ "id": "EXq68w...", "meta": null, "type": "crypto" }
],
"balance": 0,
"pendingDebit": 0
}
}
]
},
"message": "success"
}
```
### Understanding Asset IDs
The `asset` array within each currency object contains an id. For crypto assets, the `id` is the wallet address for that specific network.
To find the correct Asset ID for a specific currency and network combo, use the indexing data from the [Supported Assets](/v4/documentation/introduction#supported-assets) response:
```javascript JavaScript theme={null}
// 1. Get destination details from Supported Assets response
const assetInfo = getAssetResponse.byCurrency[selectedCurrency][selectedNetwork];
const { destinationCurrency, assetIndex } = assetInfo;
// 2. Use those details to find the correct asset in your Get Account response
const assetId = getAccountResponse.data.accounts[destinationCurrency].asset[assetIndex].id;
```
```python Python theme={null}
# 1. Get destination details from Supported Assets response
asset_info = get_asset_response['byCurrency'][selected_currency][selected_network]
destination_currency = asset_info['destinationCurrency']
asset_index = asset_info['assetIndex']
# 2. Use those details to find the correct asset in your Get Account response
asset_id = get_account_response['data']['accounts'][destination_currency]['asset'][asset_index]['id']
```
This logic ensures you always use the correct wallet address for the network and currency combination you have selected.
## 4. Set up your webhook endpoint
Before processing real transactions, configure a webhook URL to receive status updates. You can do this via the [dashboard](https://dashboard.getpartna.com/settings?tab=api-webhooks) or the [Update Webhook URL](/api-reference/endpoint/v3/user/update-webhook-url) endpoint.
Your webhook endpoint should return a `200` status code immediately upon receiving the event. Process the event asynchronously.
## Next steps
You are now authenticated and can fetch rates. Here is what to build next, depending on your use case:
| Use case | Guide |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Users convert local currency to crypto | [Onramp Guide](/v4/documentation/guides/onramp) |
| Users convert local currency to crypto with a Partna checkout | [Onramp Widget Guide](/v4/documentation/guides/onramp-widget) |
| Users convert crypto to local currency | [Offramp Guide](/v4/documentation/guides/offramp) |
| Users convert crypto to local currency with a Partna payout flow | [Offramp Widget Guide](/v4/documentation/guides/offramp-widget) |
| Collect payments and settle in USD/stablecoins | [Collect and Settle Guide](/v4/documentation/guides/collect-and-settle) |
| Verify user identity for transactions | [Accounts and KYC Guide](/v4/documentation/guides/accounts-and-kyc) |
| Test with mock transactions in staging | [Testing Guide](/v4/documentation/core-concepts/testing) |