Cross-border Status, Webhooks and Retries
Reconcile request and transaction IDs, handle lifecycle events and recover safely.
Store the sender ID, cross-border request ID, request externalReference, and each charge transaction ID separately. A request can have multiple failed charge attempts, but only one pending or successful deposit is permitted at a time.
Status lookups and identifiers
Use https://api-v2.honeycoin.app/api/b2b/cross-border/requests as {crossBorderBaseUrl}, or the corresponding sandbox base URL.
| Identifier | How to obtain it | How to use it |
|---|---|---|
| Sender ID | Create sender response data.id | Pass as senderId when creating a request |
| Request ID | Create request response data.id | Fetch request, charge, close, or list its transactions |
| Charge transaction ID | Charge response data.transactionId | Fetch transaction to inspect data.chargeStatus |
| Request external reference | Your create-request body | Reconcile with crossborder_updated.data.externalReference |
GET {crossBorderBaseUrl}/{id} expects a request ID. GET {crossBorderBaseUrl}/transactions/{id} expects a transaction ID. Neither lookup substitutes an external reference for the ID.
Request lifecycle
Request status | Meaning and next action |
|---|---|
pending_charge | Request created; initiate the charge after the customer confirms |
charge_initiated | Collection started; wait for the charge result |
charge_successful | Source funds collected; wait for the CNY payout |
charge_failed | Collection failed; reconcile the related transactions before retrying or closing |
payout_initiated | Beneficiary payout is being processed |
payout_successful | Beneficiary payout completed successfully |
payout_failed | Beneficiary payout failed; contact support and do not initiate another source charge |
refund_inititated | Refund in progress; this is the exact API spelling |
refund_successful | Refund completed; this does not indicate a successful beneficiary payout |
refund_failed | Refund failed; contact support |
closed | Request closed; further charges are unavailable. Closing itself is not payment or refund confirmation |
Transaction chargeStatus uses a separate set of lowercase values: pending, successful, and failed. A successful crossborder-deposit proves collection success only. Confirm request payout_successful to establish transfer completion.
Webhook events
Configure webhooks for the selected environment and validate webhook secrets. Acknowledge accepted deliveries promptly, process them asynchronously, and tolerate retries and delivery reordering. Use the resource lookup to reconcile stale or ambiguous updates.
The initial local charge
Initiating a charge creates a transaction with type crossborder-deposit. The transaction_created event identifies that charge using data.transactionId. Example:
{
"event": "transaction_created",
"data": {
"transactionId": "charge_example_001",
"publicKey": "ACCOUNT_PUBLIC_KEY",
"amount": 1000,
"currency": "KES",
"type": "crossborder-deposit",
"status": "pending",
"externalReference": null
},
"timestamp": "2026-09-11T08:00:00.000Z"
}The collection result arrives as transaction_updated for that same transaction:
{
"event": "transaction_updated",
"data": {
"transactionId": "charge_example_001",
"publicKey": "ACCOUNT_PUBLIC_KEY",
"amount": 1000,
"currency": "KES",
"type": "crossborder-deposit",
"status": "successful",
"method": "momo",
"externalReference": null
},
"timestamp": "2026-09-11T08:01:00.000Z"
}A charge transaction's external reference is not guaranteed to contain the request's external reference. Link it using the transaction ID returned at initiation, or list the request's transactions. Payout transactions use type: crossborder-payout.
The cross-border request
The crossborder_updated event carries the request ID in data.id, together with the request status and its external reference. It does not carry the charge ID in that field.
{
"event": "crossborder_updated",
"data": {
"id": "request_example_001",
"publicKey": "ACCOUNT_PUBLIC_KEY",
"status": "payout_successful",
"externalReference": "cny-transfer-001"
},
"timestamp": "2026-09-11T09:00:00.000Z"
}The same shape is used for other request status changes, such as charge_successful or payout_failed. Read the request for its saved quote and data.beneficiary, including recipient identity and data.beneficiary.payoutDetails. Do not deduplicate all request events by request ID alone: the same ID appears in multiple status changes. Make each business effect idempotent and reconcile the current resource state before applying conflicting updates.
Retry and recovery rules
| Situation | Action |
|---|---|
| Create call times out | Retain the same external reference. Reconcile before creating a replacement; list your requests and match their returned externalReference if the request ID was not saved |
409 DUPLICATE_EXTERNAL_REFERENCE | Find the existing request. Do not change the reference just to bypass the duplicate check |
202 with “Request is being processed” | The call has no resource ID yet. Wait, then reconcile or repeat the same call; do not assume payment succeeded |
| Charge call times out or errors | Fetch the request and list its transactions. A charge may already exist even if the HTTP call failed |
| Pending or successful deposit exists | Do not start another charge; reconcile its result |
Request is charge_failed, and all previous deposits failed | Retry POST {crossBorderBaseUrl}/charges/{requestId} with the same request ID and no body. Retain each attempt's transaction ID |
| Immediate retry returns the previous response | Repeated calls can replay a recent response. Back off and reconcile before expecting a new attempt |
payout_failed or refund failure | Contact support with the request ID, transaction IDs, and external reference. Do not collect the source funds again |
Request details cannot be edited through these endpoints. A retry uses the saved quote and collection details. If those details need changing, first establish that there is no pending or successful payment; close the unpaid request if eligible and create a new request with a new external reference.
An ineligible charge may return HTTP 400 with E-API-CBR-T.S-26 in production, or E-API-SANDBOX-CBR-CH.S-44 / E-API-SANDBOX-CBR-CH.S-52 in sandbox. State conflicts can also return 409 with CROSS_BORDER_CHARGE_IN_PROGRESS, CROSS_BORDER_REQUEST_ALREADY_CHARGED, CROSS_BORDER_REQUEST_NOT_CHARGEABLE, or CROSS_BORDER_REQUEST_CLOSED. Reconcile the saved request and transaction statuses in all these cases.
Pagination
All three list endpoints return newest records first and accept limit (1–100, default 20), after, and before. Use one cursor direction per call. Treat cursors as opaque and URL-encode them. Request the next page only when hasNext is true, using nextCursor as after; use prevCursor as before when hasPrev is true.
| Endpoint | Records | Pagination | Other filters |
|---|---|---|---|
GET {crossBorderBaseUrl}/senders | data.senders | data.pagination | None |
GET {crossBorderBaseUrl} | data | pagination | status, senderId |
GET {crossBorderBaseUrl}/{requestId}/transactions | data.transactions | pagination | status, externalReference |
For request listing, status is a request lifecycle value. For transaction listing, status is a lowercase transaction chargeStatus, and externalReference filters the transaction's own reference. Request listing does not accept an externalReference filter. Preserve the same filters as you move through pages.
Example empty transaction page:
{
"success": true,
"data": { "transactions": [] },
"pagination": {
"nextCursor": null,
"prevCursor": null,
"hasNext": false,
"hasPrev": false,
"limit": 20
}
}Close an unpaid request
Call Close a Cross-border Request: POST {crossBorderBaseUrl}/{requestId}/close, with no body.
Closing is permitted only when the request is pending_charge, charge_initiated, or charge_failed, and it has no pending or successful deposit and no payout transaction. The status alone does not establish eligibility. Closing an active charge is rejected; this endpoint does not cancel an in-flight payment or refund collected funds.
{
"success": true,
"message": "Cross border request closed successfully.",
"data": {
"id": "request_example_001",
"status": "closed",
"pendingChargesClosed": 0
}
}An already closed request returns 409 CROSS_BORDER_REQUEST_ALREADY_CLOSED. Other lifecycle conflicts return 409 CROSS_BORDER_REQUEST_CANNOT_BE_CLOSED. Re-fetch the request before deciding the next action.
Updated about 14 hours ago
