Send funds to a recipient’s registered wallet using payout sessions and executions. For collection, see Collect with Node.js.
Prerequisites
- Node.js 18+ with
fetch (or undici)
- Recipient party and counterparty (or create them first)
- Payment account for the recipient with
operation_type=PAYOUT
- Merchant passcode verification (required on payout execute)
- Server-side only — never expose Client Secret in the browser
Flow overview
- Authenticate → Bearer token
- Ensure party / counterparty exist
POST /v1/payment-accounts/create?operation_type=PAYOUT
POST /v1/sessions/payout with payout_account_id, amount, currency
POST /v1/executions/payout with session_id, payout_account_id, reference_id, and passcode
GET /v1/status/payout/:reference_id
Steps
Authenticate
const BASE_URL = "https://api.heydollr.app";
const tokenRes = await fetch(`${BASE_URL}/v1/jwt/client/obtain/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.DOLLR_CLIENT_ID,
client_secret: process.env.DOLLR_CLIENT_SECRET,
}),
});
const tokenData = await tokenRes.json();
const headers = {
Authorization: `Bearer ${tokenData.access_token}`,
"Content-Type": "application/json",
};
Payment account (recipient wallet)
const account = await fetch(
`${BASE_URL}/v1/payment-accounts/create?operation_type=PAYOUT`,
{
method: "POST",
headers,
body: JSON.stringify({
account_name: "Beneficiary MTN Wallet",
provider: "MTN_MOMO_LBR",
method: "MTN_MOMO_LBR",
party_id: partyId,
country_code: "LR",
insensitive_account_number: "231771234567",
}),
}
).then((r) => r.json());
Use MMO prediction to resolve method and provider from phone when possible.Payout session
const session = await fetch(`${BASE_URL}/v1/sessions/payout`, {
method: "POST",
headers,
body: JSON.stringify({
payout_account_id: account.id,
amount: 100.0,
currency: "USD",
}),
}).then((r) => r.json());
The session response includes expires_at and wallet_id (the source wallet Dollr debits). You do not send wallet_id or expires_at on create.Execute payout
const referenceId = crypto.randomUUID();
const execution = await fetch(`${BASE_URL}/v1/executions/payout`, {
method: "POST",
headers,
body: JSON.stringify({
session_id: String(session.id),
payout_account_id: String(account.id),
reference_id: referenceId,
passcode: {
token: {
phone: "231771234567",
code: process.env.MERCHANT_PASSCODE,
},
device: {
name: "Payout Server",
type: "SERVER",
platform: "NODE",
user_agent: "dollr-payout/1.0",
client_ip: "203.0.113.10",
},
},
}),
}).then((r) => r.json());
Persist referenceId in your database before awaiting the response. The passcode object is required — it verifies the merchant authorizing the payout.Poll status
const status = await fetch(
`${BASE_URL}/v1/status/payout/${referenceId}`,
{ headers }
).then((r) => r.json());
console.log(status.status); // PENDING → PROCESSING → COMPLETED | FAILED
Do not submit a second payout with a new reference_id while status is PROCESSING.
Passcode payload
| Field | Required | Description |
|---|
passcode.token.code | Yes | Merchant passcode |
passcode.token.phone | No* | Phone tied to the passcode |
passcode.token.user_id | No* | User identifier alternative to phone |
passcode.device.name | Yes | Device label for audit |
passcode.device.type | Yes | e.g. SERVER, MOBILE |
passcode.device.platform | Yes | e.g. NODE, IOS |
passcode.device.user_agent | Yes | Client user agent string |
passcode.device.client_ip | Yes | Originating IP address |
* Provide phone or user_id on token as required by your merchant account setup.
Obtain your payout passcode
- Log in to merchant.heydollr.app
- Go to Settings → Security (or Profile → Passcode)
- Create or reset your merchant passcode
- Use the same phone in
passcode.token.phone as registered in the portal
If passcode settings are not visible, your account may need full verification — see Forbidden 403. Troubleshooting: Payout passcode errors.
Wallet funding
Payouts debit your Dollr wallet (wallet_id appears on the session response). Ensure sufficient balance before executing — see Insufficient wallet balance.
Last modified on June 23, 2026