Skip to main content

Simulating Payments

When integrating with Hubpay's collections API, you need to verify that your system correctly handles payment lifecycle events — status transitions, webhook notifications, and payment confirmations. In production, this requires real bank transfers or cryptocurrency transactions. In sandbox, you can simulate the entire payment flow with a single API call.

This tutorial walks you through creating a payment request, simulating a payment, and verifying the results — giving you confidence that your integration handles real payments correctly before going live.


Prerequisites

Before you begin, make sure you have:

New to Hubpay?

If you haven't set up your sandbox environment yet, start with the Getting Started guide.


What the simulation does

The simulate endpoint triggers the same lifecycle as a real payment:

Bank transfer simulation:

  1. Payment reported as sent → payment.pending webhook
  2. Funds received → payment.received webhook
  3. Payment request status updated → payment_request.paid webhook
  4. Funds settled to wallet → payment.completed webhook

Crypto simulation:

  1. Deposit detected → payment.pending webhook
  2. Deposit confirmed on-chain → payment.received webhook
  3. Payment request status updated → payment_request.paid (or part_paid) webhook
  4. Settlement complete → payment.completed webhook (only when fully paid)

Card simulation:

  1. Payment authorised → payment.pending webhook
  2. Payment captured → payment.received webhook
  3. Payment request status updated → payment_request.paid webhook
  4. Funds settled to wallet → payment.completed webhook

Your webhook handler receives the same events in the same order as production — making sandbox simulation a reliable way to validate your integration.


Step 1: Create a payment request

Before you can simulate a payment, you need a payment request in UNPAID status. Create one using the Create Payment Request endpoint. You can include payer details inline or reference an existing payer — see the API reference for all available options.

Make sure to enable the payment method you want to simulate (BANK_TRANSFER, CRYPTO, and/or CARD) in the paymentMethods array.

Note the id from the response — you'll use this as the paymentRequestId in the next step.


Step 2: Simulate a payment

Use the Simulate Payment endpoint with your payment request ID and chosen payment method. If no amount is specified, the full remaining balance is used.

Bank transfer example:

{
"paymentRequestId": "d4f7a8b2-1234-5678-9abc-def012345678",
"paymentMethod": "BANK_TRANSFER"
}

Crypto example:

{
"paymentRequestId": "d4f7a8b2-1234-5678-9abc-def012345678",
"paymentMethod": "CRYPTO",
"cryptoCurrency": "USDT"
}

Card example:

{
"paymentRequestId": "d4f7a8b2-1234-5678-9abc-def012345678",
"paymentMethod": "CARD"
}

The response confirms the simulation succeeded and includes the simulated payment ID, reference, and updated payment request status. See the API reference for the full request and response schema.

The payment request is now fully paid. Your webhook endpoint should have received events in sequence — see the webhook tables below for the exact events per method.


Step 3: Verify the results

Check the payment request status

Query the payment request to confirm the status update:

curl https://sandbox-api.hubpay.io/v1/collections/payment-requests/d4f7a8b2-1234-5678-9abc-def012345678 \
-H "Authorization: Bearer <YOUR_TOKEN>"

Response:

{
"id": "d4f7a8b2-1234-5678-9abc-def012345678",
"status": "PAID",
"amount": 1000.00,
"amountPaid": 1000.00,
"amountRemaining": 0,
"payments": [
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "COMPLETE",
"sendAmount": 1000.00,
"sendCurrency": "AED",
"receiveAmount": 1000.00,
"receiveCurrency": "AED",
"method": "BANK_TRANSFER"
}
]
}

Verify webhook delivery

Check your webhook endpoint. You should see events delivered in this order:

Bank transfer:

#EventDescription
1v1.collection.payment_request.payment.pendingPayment has been initiated
2v1.collection.payment_request.payment.receivedFunds received
3v1.collection.payment_request.paidPayment request fully paid
4v1.collection.payment_request.payment.completedFunds settled to wallet

Crypto:

#EventDescription
1v1.collection.payment_request.payment.pendingDeposit detected
2v1.collection.payment_request.payment.receivedDeposit confirmed on-chain
3v1.collection.payment_request.paidPayment request fully paid
4v1.collection.payment_request.payment.completedSettlement complete

Card:

#EventDescription
1v1.collection.payment_request.payment.pendingPayment authorised
2v1.collection.payment_request.payment.receivedPayment captured
3v1.collection.payment_request.paidPayment request fully paid
4v1.collection.payment_request.payment.completedFunds settled to wallet

Step 4: Test underpayments and multiple payments

Hubpay does not offer customers the option to choose a partial payment amount — the hosted payment page always presents the full invoice amount. However, in practice you have no control over how much a customer actually sends via bank transfer, or how much cryptocurrency arrives on-chain. A customer might send less than the invoiced amount, or split a payment across multiple transactions.

Your integration should handle these scenarios, and the simulation endpoint lets you test them by specifying an amount less than the remaining balance:

curl -X POST https://sandbox-api.hubpay.io/v1/collections/simulate-payment \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"paymentRequestId": "d4f7a8b2-1234-5678-9abc-def012345678",
"paymentMethod": "BANK_TRANSFER",
"amount": 400.00
}'

Response:

{
"paymentRequestId": "d4f7a8b2-...",
"paymentId": "...",
"paymentMethod": "BANK_TRANSFER",
"amount": 400.00,
"currency": "AED",
"paymentRequestStatus": "PART_PAID"
}

The payment request status is PART_PAID. You can simulate additional payments until the full amount is reached. Each simulation represents a separate payment from the customer — for example, two bank transfers of 400 AED and 600 AED against a 1,000 AED invoice.

Why test underpayments?

In production, a customer might:

  • Send a bank transfer for a different amount than invoiced
  • Deposit less cryptocurrency than required (e.g., due to network fees)
  • Make multiple smaller payments over time

Your system should listen for payment_request.part_paid webhooks and decide how to handle partial fulfilment — whether that's sending a reminder, adjusting the invoice, or waiting for the remainder.

Crypto underpayments

Crypto payments work differently from bank transfers when underpaid. When a customer deposits less than the invoiced amount:

  • The deposit is confirmed on-chain and a payment.received webhook is sent
  • The payment request moves to PART_PAID and a payment_request.part_paid webhook is sent
  • No settlement occurs yet — the customer has until the quote window expires to send the remaining amount
  • If the full amount is received before expiry, all deposits are settled together — triggering payment.completed, wallet balance update, and auto-payout
  • If the quote window expires with only a partial amount received, the invoice auto-settles with whatever has been received at that point

In production, if the quote window expires with a partial amount, the invoice auto-settles whatever was received — meaning you can receive payment.completed and payout webhooks for less than the full invoiced amount. Your integration should handle this scenario.

In the sandbox simulation, settlement only triggers when the total simulated amount reaches the full invoice value. To test the complete webhook lifecycle including payment.completed and payout, simulate payments that add up to the full amount. Partial crypto simulations will only produce payment.received and payment_request.part_paid events until then.


Step 5: Test with auto-payout

If your payment request includes payout details (a linked beneficiary), the simulation will automatically create a payout to that beneficiary after settlement — just like production.

To test this, create a payment request with payoutDetails including a beneficiaryId, reference, and purposeOfPayment. See the Payouts guide for details on how auto-payout works and how to set up beneficiaries.

Then simulate the payment. Once settlement completes, the payout is created automatically.

info

Auto-payout is only supported for AED payment requests with a valid UAE beneficiary.


Simulation rules

RuleDetail
Sandbox onlyThis endpoint is not available in production
One method per requestYou cannot mix payment methods (bank transfer, crypto, card) on the same payment request
UNPAID or PART_PAIDThe payment request must not already be fully paid or cancelled
Amount limitIf specified, amount must not exceed the remaining balance
AED onlyPayouts are only created for AED currency payment requests

Request reference

POST /v1/collections/simulate-payment

FieldTypeRequiredDescription
paymentRequestIdUUIDYesThe payment request ID from POST /v1/collections/payment-requests
paymentMethodStringYesBANK_TRANSFER, CRYPTO, or CARD
amountNumberNoAmount to simulate. Defaults to the full remaining balance
cryptoCurrencyStringNoUSDT or USDC. Defaults to USDT. Only used with CRYPTO

Response

FieldTypeDescription
paymentRequestIdUUIDThe payment request ID
paymentIdUUIDThe simulated payment ID
paymentReferenceStringUnique reference for the simulated payment
paymentMethodStringThe payment method used
amountNumberThe amount simulated
currencyStringThe payment currency
paymentRequestStatusStringUpdated status: PAID or PART_PAID

Next steps