Payment lifecycle and webhooks
Hubpay tracks three related resources:
- A payment request tracks whether the requested balance is unpaid, partly paid or fully paid.
- Each payment tracks one card payment, bank transfer or crypto deposit through receipt and wallet credit.
- An optional payout tracks a separate outbound transfer after a payment completes.
For crypto integrations, use v1.collection.payment_request.payment.completed, then fetch the payment request and confirm that the matching payment has status COMPLETE.
v1.collection.payment_request.payment.received only confirms that the crypto deposit was confirmed on-chain. Sender verification, KYT, remitter screening, conversion, reconciliation and Hubpay wallet credit may still be outstanding.
States at a glance
| Resource | API state | Meaning |
|---|---|---|
| Payment request | UNPAID | No confirmed amount has been applied to the request |
| Payment request | PART_PAID | Some confirmed value has been applied, but a balance remains |
| Payment request | PAID | The full requested value has been received or confirmed; individual payments may still be processing |
| Payment request | CANCELLED | The request was cancelled by the user or after a payment was rejected |
| Payment | PENDING | Payment initiated, authorised, reported as sent or awaiting confirmation |
| Payment | RECEIVED | Funds were captured or confirmed by the payment rail; for crypto, compliance checks, conversion, reconciliation and wallet credit may still be in progress |
| Payment | COMPLETE | The payment completed, all applicable crypto compliance gates cleared and your Hubpay wallet was credited |
| Payment | FAILED | The payment failed or was rejected |
A payment request can be PAID while an individual payment is still RECEIVED. This means the request balance is covered, while later processing such as conversion, reconciliation or wallet credit may still be in progress.
Webhook sequence by payment method
| Step | Card | Bank transfer | Crypto | Webhook |
|---|---|---|---|---|
| Payment in progress | Card authorised | Customer reports transfer sent | Queued immediately before payment.received when a confirmed deposit is processed | v1.collection.payment_request.payment.pending |
| Funds observed | Card captured | Funds received by Hubpay | Deposit confirmed on-chain | v1.collection.payment_request.payment.received |
| Request balance updated | Request becomes PAID or PART_PAID | Request becomes PAID or PART_PAID | Request becomes PAID or PART_PAID | v1.collection.payment_request.paid or .part_paid |
| Compliance and internal processing | — | — | Sender verification, KYT, remitter screening, conversion and settlement reconciliation | No additional public payment event; payment remains RECEIVED |
| Wallet credit | Funds credited to Hubpay wallet | Funds credited to Hubpay wallet | Provider settlement reconciled and funds credited to Hubpay wallet | v1.collection.payment_request.payment.completed |
These are lifecycle transitions, not a delivery-order guarantee. Webhook retries can arrive more than once or out of order. Verify the signature, deduplicate the event, and fetch current API state before changing your own records.
Crypto lifecycle
For crypto, the current backend queues payment.pending immediately before payment.received when it processes a provider-confirmed deposit. Do not use the crypto pending event as evidence that confirmation is still outstanding.
After on-chain confirmation, Hubpay verifies the sender, starts KYT where enabled and checks remitter screening. An internal compliance hold is exposed through the payment API as RECEIVED and does not produce a separate hold webhook. If a check rejects the crypto payment, the payment request can be cancelled and the payment is exposed as FAILED when queried.
The provider's conversion-executed and fiat-settlement callbacks are also internal processing stages. They do not emit a public payment webhook or change the public payment status. For crypto, use payment.completed plus status COMPLETE; this confirms the applicable compliance gates and settlement checks passed and the Hubpay wallet was credited.
For a full crypto payment, payment.received and payment_request.paid can arrive before payment.completed. For an underpayment, the request becomes PART_PAID; settlement can wait for the remaining amount or occur when the quote window expires. See simulating payments for the partial-payment rules.
Recommended integration flow
- Receive the webhook and verify its HMAC signature against the raw request body.
- Deduplicate using the webhook event identifier.
- Read
data.paymentRequestIdanddata.paymentIdfrom the event. - Fetch
GET /v1/collections/payment-requests/{id}. - Find the matching payment and check the state required by your workflow.
- Apply the business action once, then return
2xxpromptly.
For a crypto success workflow, require all three checks:
- The event is
v1.collection.payment_request.payment.completed. - The event signature is valid.
- The matching payment returned by the API has status
COMPLETE.
Do not use the browser redirect, checkout success screen, payment_request.paid, payment.received or payment status RECEIVED as final crypto confirmation.
Choose the right signal
| Business action | Recommended signal |
|---|---|
| Show crypto processing progress | payment.pending or payment.received; do not treat either as final confirmation |
| Show the requested balance is covered | payment_request.paid plus current API state; individual payments may still be processing |
| Confirm a crypto payment | payment.completed plus payment status COMPLETE |
| Confirm funds are available in the Hubpay wallet | payment.completed plus payment status COMPLETE |
| Track an automatic payout to a beneficiary | payment.payout.* events; see Payouts |