Transactions API
Create and track money transfers.
Create Transaction
Withdraw (Default)
Send funds to a customer's payment method:
const transaction = await afriex.transactions.create({
customerId: "customer-id",
sourceAmount: "10",
destinationAmount: "5000",
sourceCurrency: "USD",
destinationCurrency: "NGN",
destinationId: "payment-method-id",
meta: {
idempotencyKey: "unique-key-123",
reference: "order-456",
narration: "Payment for services",
},
});Deposit
Pull funds from a customer's payment method:
const transaction = await afriex.transactions.create({
customerId: "customer-id",
type: "DEPOSIT",
sourceAmount: "10",
destinationAmount: "5000",
sourceCurrency: "USD",
destinationCurrency: "NGN",
sourceId: "source-payment-method-id",
meta: {
idempotencyKey: "unique-key-456",
reference: "deposit-789",
},
});Swap
Exchange funds between currencies in the business wallet:
const transaction = await afriex.transactions.create({
type: "SWAP",
sourceAmount: "10",
sourceCurrency: "USD",
destinationCurrency: "NGN",
meta: {
idempotencyKey: "unique-key-789",
reference: "swap-101",
},
});Parameters
| Field | Type | Required | Description |
|---|---|---|---|
customerId | string | Yes for DEPOSIT and WITHDRAW | Customer ID. Optional for SWAP when using the business wallet |
sourceAmount | number | ${number} | One of the two amounts | Amount in source currency. Send it alone and the API derives the destination amount at the live rate |
destinationAmount | number | ${number} | One of the two amounts | Amount in destination currency. Send it alone and the API derives the source amount. A SWAP takes exactly one of the two |
sourceCurrency | string | Yes | Source currency (e.g., USD) |
destinationCurrency | string | Yes | Destination currency (e.g., NGN) |
type | string | - | WITHDRAW (default), DEPOSIT, or SWAP |
destinationId | string | Yes for WITHDRAW | Payment method to send to |
sourceId | string | Yes for DEPOSIT | Payment method to pull from |
meta.idempotencyKey | string | Yes | Unique key to prevent duplicates |
meta.reference | string | Yes | Your internal reference |
meta.narration | string | - | Transaction description |
meta.invoice | string | Yes for SWIFT withdrawals | Object key of the uploaded invoice, as returned by the media upload endpoint. The file must already exist in storage |
meta.settlement | "spot" | "request" | - | spot (default) debits the Payout wallet at once. request is for WITHDRAW only: it debits the Collection wallet and queues the payout for the next business day |
correspondentBankName | string | - | Correspondent bank name for a USD payout with meta.settlement: "request". A fallback for the value on the payment method. Send it together with correspondentBankAccountNumber |
correspondentBankAccountNumber | string | - | Correspondent bank account number. Send it together with correspondentBankName |
shouldPreferSourceAmount | boolean | - | Derive destinationAmount from sourceAmount via the forward rate even when both are sent. Defaults to false (destination-wins) |
Get Transaction
const transaction = await afriex.transactions.get("transaction-id");List Transactions
const response = await afriex.transactions.list({
page: 0,
limit: 20,
status: ["PENDING", "PROCESSING"],
currency: ["USD", "NGN"],
fromDate: "2025-01-01T00:00:00.000Z",
toDate: "2025-01-31T23:59:59.999Z",
});
console.log(response.data); // Transaction[]Supported filters:
| Field | Type | Description |
|---|---|---|
transactionId | string | Filter by Afriex transaction ID |
reference | string | Filter by meta.reference |
status | string | string[] | One or more transaction statuses |
type | string | string[] | One or more transaction types |
channel | string | string[] | One or more source or destination payment channels |
currency | string | string[] | One or more 3-letter currency codes |
fromDate | string | ISO 8601 lower bound for creation time |
toDate | string | ISO 8601 upper bound for creation time |
Authorize Transaction
Complete a transaction left in CUSTOMER_ACTION_REQUIRED that needs an extra authorization step — for example, an OTP on a mobile-money deposit (meta.otpRequired: true). Today the only supported variant is OTP.
const transaction = await afriex.transactions.authorize("transaction-id", {
type: "OTP",
otp: "123456",
});Submit a Pool-Account Payment Proof
When a customer pays into your pool account, submit the proof of payment so the deposit can be confirmed and credited.
import { readFile } from "node:fs/promises";
const proof = await afriex.media.upload({
fileName: "transfer-receipt.pdf",
type: "transaction",
file: await readFile("./transfer-receipt.pdf"),
});
const deposit = await afriex.transactions.submitPoolAccountProof({
amount: 5000,
customerId: customer.customerId,
countryCode: "NG",
reference: customer.reference,
fileKey: proof.key,
timestamp: new Date("2026-05-18T10:00:00.000Z"),
senderDetails: { name: "John Doe", bankName: "GTBank" },
});
console.log(deposit.status); // IN_REVIEWParameters:
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | The deposit amount, in the major currency unit |
customerId | string | Yes | The customer to credit. It must be the customer reference names |
countryCode | string | Yes | ISO 3166-1 alpha-2 code of the pool account |
reference | string | Yes | customer.reference to credit a customer, or the pool account's own reference to credit the business |
fileKey | string | Yes | The key of the uploaded proof. Upload it with type: "transaction" |
timestamp | string | Date | Yes | When the payment was sent |
senderDetails | object | - | name (required when the object is sent), accountNumber, bankName, countryCode |
The deposit is created IN_REVIEW. An operator then confirms the bank inflow: approval arrives as a TRANSACTION.CREATED webhook, rejection as POOL_DEPOSIT_REQUEST.REJECTED.
A second submission that matches on every field is rejected with 409.
Get a Settlement Advice
A USD withdrawal created with meta.settlement: "request" gets a settlement and remittance advice, as a PDF.
const advice = await afriex.transactions.getAdvice("transaction-id");
console.log(advice.url, advice.status, advice.version);| Field | Type | Description |
|---|---|---|
url | string | Download URL for the PDF, valid for 5 minutes |
reference | string | The advice reference. It stays the same when the advice is regenerated |
status | "PENDING" | "COMPLETED" | PENDING until the withdrawal succeeds |
version | number | Starts at 1 and goes up when the advice is regenerated |
generatedAt | string | When this version was generated |
Any other transaction answers 404. The advice is not a tax invoice.
Simulate an Outcome (Sandbox)
Complete a pending sandbox transaction now, with the outcome you choose.
const transaction = await afriex.transactions.simulate("transaction-id", {
outcome: "failed", // or "success"
});The transaction must still be PENDING, PROCESSING or UNKNOWN. A deposit waiting for an OTP has to be authorized first.
simulate() returns the transaction as it stands before it is finalized. The final status arrives a few seconds later through the TRANSACTION.UPDATED webhook, or on the next get().
Sandbox only. Production answers 403.
Status Values
| Status | Description |
|---|---|
PENDING | Created, awaiting processing |
PROCESSING | Currently processing |
SUCCESS | Successful |
FAILED | Failed |
CANCELLED | Cancelled |
REFUNDED | Refunded |
RETRY | Retrying |
UNKNOWN | Status unknown |
SCHEDULED | Scheduled for later execution |
CUSTOMER_ACTION_REQUIRED | Waiting on customer action — see Authorize Transaction |
REJECTED | Rejected |
IN_REVIEW | Under review |
DISPUTED | Dispute opened |
DISPUTE_RESOLVED | Dispute resolved |
DISPUTE_WON | Dispute resolved in your favor |
DISPUTE_LOST | Dispute resolved against you |
DISPUTE_EVIDENCE_SUBMITTED | Evidence submitted for dispute |
Failure Reasons
When status is FAILED or REJECTED, meta.failureReason carries a stable Afriex-side code (AFX_*, e.g. AFX_VELOCITY_LIMIT_EXCEEDED), a customer-safe message, and a retryable boolean — switch on code, not message.