Move money with API
Send your first transfer in five steps, from finding your wallet to seeing the status update.
This guide gets you to your first successful disbursement on PayMongo. The path uses the live API but works the same in test mode — switch your secret key to start in a sandbox and move to live when you're ready.
Prerequisites
- A PayMongo merchant account with at least one wallet activated.
- Your secret API key from the dashboard (test or live).
- The recipient's bank account number, account name, and the bank's BIC. If you don't have the BIC, list it from
GET /v2/transfers/receiving_institutions?provider=instapay(orpesonet).- For test case account number, refer to this Wallet Test Case Docs
1. Authenticate
Every request uses HTTP Basic Auth: your secret key as the username, with an empty password.
See Wallet API Docs
curl https://api.paymongo.com/v2/wallets \
-u sk_test_xxxxxxxxxxxxxxxxxxxxxxxx:If the call returns 401 Unauthorized, double-check that you're using a secret key (starts with sk_test_ or sk_live_) and not a public key, and that the trailing colon is present.
2. Find your wallet
List your wallets to get the wallet ID and confirm you have funds available. The source_account you use for creating transfer must match one of the accounts attached to your wallet. See the Retrieve a Wallet reference for the full response.
curl https://api.paymongo.com/v2/wallets \
-u sk_test_xxxxxxxxxxxxxxxxxxxxxxxx:Take note of the wallet's source_account.number, name, and bic — you'll plug these straight into the transfer payload.
3. Submit a transfer
Send funds by creating a batch transfer. Even a single transfer goes through this endpoint — wrap it in a transfers array of one. Amounts are in the currency's smallest unit (for PHP, centavos: 10000 = ₱100.00). See Create Batch Transfer for the full request and response schema.
curl https://api.paymongo.com/v2/batch_transfers \
-u sk_test_xxxxxxxxxxxxxxxxxxxxxxxx: \
-H "Content-Type: application/json" \
-d '{
"transfers": [
{
"provider": "instapay",
"amount": 10000,
"currency": "PHP",
"purpose": "Disbursement",
"description": "Driver weekly payout",
"reference_number": "ref-001",
"source_account": {
"number": "0000000001",
"name": "Your Business Inc.",
"bic": "PAEYPHM2XXX"
},
"destination_account": {
"number": "1234567890",
"name": "Juan Dela Cruz",
"bic": "BNORPHMM"
},
"callback_url": "https://yourapp.com/webhooks/paymongo",
"metadata": { "driver_id": "drv_42" }
}
]
}'Response
A successful call returns a batch_transfer with the individual transfers it spawned. Persist both data.id (batch transfer ID) and each transfers[].id — you'll use them to reconcile status updates.
{
"data": {
"id": "btr_xxxxxxxxxxxxxxxx",
"transfers": [
{
"id": "tr_xxxxxxxxxxxxxxxx",
"status": "pending",
"provider": "instapay",
"amount": 10000,
"fee": 800,
"currency": "PHP",
"reference_number": "ref-001",
"provider_reference_number": null,
"source_account": { "number": "0000000001", "name": "Your Business Inc.", "bic": "PAEYPHM2XXX" },
"destination_account": { "number": "1234567890", "name": "Juan Dela Cruz", "bic": "BNORPHMM" },
"batch_transfer_id": "btr_xxxxxxxxxxxxxxxx",
"metadata": { "driver_id": "drv_42" },
"created_at": 1713657600,
"updated_at": 1713657600
}
]
}
}Transfers within a batch sharing the same source_account are processed FIFO, so ordering in the transfers array is preserved.
4. Track the transfer status
Your transfer is accepted as pending and stays there until the rail settles it. Watch for the status to change in one of two ways below.
For the full list of statuses and per-rail settlement timings, see Transfers.
Use webhook to observe status change
Set callback_url on the transfer. PayMongo will POST the updated transfer resource to that URL when the status transitions. Respond with 2xx within a few seconds; non-2xx responses are retried with backoff. See Statuses, Messages, Webhook Events for the event payload and retry behavior.
5. Handle failures
When a transfer comes back as failed, the response includes a failure code and message you can surface to ops or to the recipient. A few of the most common ones:
invalid_destination_account— account number/name mismatch at the beneficiary bank. Fix the details and submit a new transfer.insufficient_wallet_balance— top up your wallet before retrying.receiving_institution_unavailable— destination bank is temporarily offline. Retry later.
The full list is in Transfers Error Codes. Always use a new, unique reference_number on retry to keep your records clean.
Going live
When your integration works end to end in test mode:
- Swap
sk_test_...for yoursk_live_...secret. - Register a production
callback_urlwith HTTPS and verified TLS. - Confirm your live wallet has been funded and the
source_accountBIC matches what PayMongo provisioned.
That's it — you've shipped your first disbursement.
Updated about 4 hours ago