Skip to main content

Journey 4: Pay out to a beneficiary

You prove that when a payment request with payout details is paid, Hubpay creates a payout to your beneficiary, your webhook receives every payout event, and your system treats the money as delivered only when the payout is PAID.

You need

  • Before you start done.
  • Payouts are available for AED payment requests paying a beneficiary with a UAE bank account.

In production two things happen outside your control: a checker in your company may have to approve the payout in the Hubpay portal, and the bank reports whether it was paid. In sandbox you play both roles with one endpoint.

Step 1: Create a beneficiary

Send POST /v1/beneficiaries. The smallest valid body for a UAE business, with your own values in the placeholders:

{
"externalId": "<YOUR_BENEFICIARY_REFERENCE>",
"personalDetails": {
"type": "BUSINESS",
"name": "<BENEFICIARY_LEGAL_NAME>",
"addressLine1": "<BENEFICIARY_ADDRESS_LINE_1>",
"country": "AE"
},
"bankDetails": {
"accountNumber": "<UAE_IBAN>",
"bic": "<BANK_BIC>",
"currency": "AED",
"bankCountry": "AE"
}
}

Save the id from the response. A 409 means a beneficiary with that account number already exists: use the existing one. See Create beneficiary.

Step 2: Create the payment request with payout details

Create an AED payment request as in Journey 1, and add:

"payoutDetails": {
"payoutBeneficiaryId": "<beneficiary id from step 1>",
"payoutReference": "<YOUR_PAYOUT_REFERENCE>",
"payoutPurposeOfPayment": "PROPERTY_RENTAL"
}

payoutPurposeOfPayment takes one value from the list in the API reference. PROPERTY_RENTAL, PROPERTY_PURCHASE, CONSTRUCTION_EXPENSES and SERVICE_CHARGES are the common ones for property payments.

Step 3: Pay the request

The quickest way is one bank transfer simulation. Send POST /v1/collections/simulate-payment:

{
"paymentRequestReference": "ORDER-10045",
"paymentMethod": "BANK_TRANSFER"
}

Save the paymentId from the response. Bank transfer and card payments complete in this call, and Hubpay creates the payout at once. For crypto, the payout is created when you complete the payment in Journey 1, step 2.

Step 4: Approve the payout, if your account needs it

Read the payout with Get payment for payment request and look at its status.

  • INITIATED: your account has no second-approver rule. Skip to step 5.

  • PENDING_APPROVAL: your account has maker-checker on. In production a checker approves in the Hubpay portal. In sandbox, send POST /v1/collections/simulate-payout:

    {
    "paymentRequestReference": "ORDER-10045",
    "paymentId": "<paymentId from step 3>",
    "outcome": "APPROVED"
    }

    Expect payoutStatus: "INITIATED" and a payout.initiated webhook. To test the other branch, send "outcome": "REJECTED" instead, with an optional failureReason. The payout becomes FAILED and your webhook receives payout.failed.

Step 5: Let the bank pay, or fail

Send POST /v1/collections/simulate-payout with "outcome": "PAID":

{
"paymentRequestReference": "ORDER-10045",
"paymentId": "<paymentId from step 3>",
"outcome": "PAID"
}

To test the failure path instead, use "outcome": "FAILED" with "failureReason": "Beneficiary account closed".

outcomePayout becomesWebhookYour app should
PAIDPAIDpayout.paidMark the money as delivered to the beneficiary
FAILEDFAILEDpayout.failedShow the money as received by you but not delivered, and tell someone

Step 6: Prove it

CheckExpected
Get payment for payment requestpayout status: "PAID" or "FAILED"
Your webhook URL, no maker-checkerpayment.completed, payout.initiated, then payout.paid or payout.failed
Your webhook URL, maker-checker and APPROVEDpayment.completed, payout.pending_approval, payout.initiated, then payout.paid or payout.failed
Your webhook URL, maker-checker and REJECTEDpayment.completed, payout.pending_approval, then payout.failed. No payout.initiated is sent
Your appTreats the payout as delivered only on PAID. payment.completed means the money reached your wallet, not the beneficiary

Each simulate-payout call moves the payout one step. A step that does not fit the current status is refused with a 400 that names the status, so PAID on a payout still awaiting approval tells you to send APPROVED first. Repeating an outcome the payout already reached returns alreadyProcessed: true and no webhook, which is how you test that your system handles duplicates.

If it does not work

ProblemCauseFix
No payout after the payment completesThe request is not AED, has no payoutDetails, or the beneficiary's bank is not in the UAECreate the request again with valid payout details
404 on simulate-payoutThe paymentId is not on this request, or the payment is not COMPLETE yetComplete the payment first, then use its paymentId
400 on simulate-payoutThe outcome does not fit the payout's current statusRead the status in the message and send the step it allows
Payout stays INITIATED and you did nothingExpected. Nothing moves a sandbox payout on its ownSend PAID or FAILED

Good to know

  • A payout failure never undoes the wallet credit. The money stays in your Hubpay wallet, and the payout can be retried by your operations team in production.
  • A part-paid request creates one payout per completed payment, each for that payment's amount. Move each one separately.
  • The API returns payout statuses in uppercase. Webhook names stay lowercase.