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 card simulation. Send POST /v1/collections/simulate-payment:

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

Save the paymentId from the response. The card payment completes in this call and Hubpay creates the payout at once. One sandbox difference: a simulated card payment does not add to your sandbox wallet balance, so do not check the balance in this journey. Crypto works too, and does credit the wallet: the payout is created when you complete the payment in Journey 1, step 2.

Do not use BANK_TRANSFER for this journey. A simulated bank transfer creates the payout and sends its webhook, but sandbox cannot attach that payout to the payment, so step 4 onwards cannot find it.

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: Send the bank's result yourself​

In production the bank reports whether the payout was paid or failed. In sandbox there is no bank behind the payout, so nothing arrives on its own and the payout stays INITIATED until you act. You send the result. 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".

Still PENDING_APPROVAL?

PAID and FAILED work only after the payout is INITIATED. If you skipped step 4, the call returns 400 with a message that says the payout is waiting for approval and to send APPROVED first. Do step 4, then send this call again.

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 payment finished on your side, not that the beneficiary was paid

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
Payout webhook arrived but the payment shows no payoutYou paid with a simulated BANK_TRANSFER, which cannot be linked to its payout in sandboxThe request is already PAID, so create a new AED request with payoutDetails and CARD or CRYPTO in paymentMethods, then repeat from step 2
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 however long you waitExpected. No bank is connected in sandbox, so nothing moves a payout on its ownSend PAID or FAILED in step 5

Good to know​

  • A payout failure never undoes the payment. In production the money stays in your Hubpay wallet and the payout can be retried by your operations team.
  • 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.