API Reference · Payments

Held Transfers

Bank transfers to a checkout’s one-time account that were the wrong amount, or arrived after it closed. Each is held out of your available balance until you accept it as a payment or return it to the customer.

The held_transfer object

Represents a held_transfer entity in the rafiqPay system.

FieldType & FlagsDescription
id
string
Held transfer ID (htr_...).
status
enum
held | accepted | returning | returned | refunded_by_provider. refunded_by_provider means the bank sent the money back to the sender itself, and there is nothing to decide.
reason
enum
mismatch (the wrong amount) | expired (arrived after the account closed).
expectedAmount
string
What the checkout asked for, in naira.
receivedAmount
string
What arrived, in naira.
senderName
stringNullable
The sender’s name, as the bank gave it.
chargeId
string
The checkout’s charge. It is not changed by this transfer.
checkoutSessionId
string
The checkout session the account was opened for.
acceptedChargeId
stringNullable
The new charge made when you accepted it.
returnPayoutId
stringNullable
The payout (trf_...) that sends it back, once you return it.
note
stringNullable
A short note, for example when the bank returned it.
createdAt
string
When the transfer arrived (ISO 8601).
decidedAt
stringNullable
When it was accepted or returned.
Example held_transfer JSON
held_transfer example payload
{
  "id": "htr_01J9KANZU7",
  "object": "held_transfer",
  "status": "held",
  "reason": "mismatch",
  "currency": "NGN",
  "expectedAmount": "35000.00",
  "receivedAmount": "30000.00",
  "senderName": "AMINA BELLO",
  "chargeId": "pay_01J9KANZU881",
  "checkoutSessionId": "cs_test_kanzu_99182736",
  "acceptedChargeId": null,
  "returnPayoutId": null,
  "note": null,
  "createdAt": "2026-11-07T10:42:00.000Z",
  "decidedAt": null
}

Endpoints & Operations

GET/v1/held-transfersIn plan 035

List held transfers

Lists held transfers, newest first (up to 200). Filter with status and mode.

Request Parameters
ParameterTypeRequirementDescription
statusenumOptionalheld | accepted | returning | returned | refunded_by_provider.
modeenumOptionallive | test. Defaults to live.
Request Example
GET /v1/held-transfers
cURL · v1
curl -X GET "https://api.rafiqpay.net/v1/held-transfers" \
  -H "Authorization: Bearer rp_test_sk_kanzu_9f8a7b6c5d4e3f2a"
Response (200 OK)
Response payload
{
  "object": "list",
  "data": [
    {
      "id": "htr_01J9KANZU7",
      "status": "held",
      "reason": "mismatch",
      "receivedAmount": "30000.00"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
GET/v1/held-transfers/:idIn plan 035

Retrieve a held transfer

Retrieves one held transfer.

Request Example
GET /v1/held-transfers/:id
cURL · v1
curl -X GET "https://api.rafiqpay.net/v1/held-transfers/:id" \
  -H "Authorization: Bearer rp_test_sk_kanzu_9f8a7b6c5d4e3f2a"
Response (200 OK)
Response payload
{
  "id": "htr_01J9KANZU7",
  "object": "held_transfer",
  "status": "held",
  "reason": "mismatch",
  "expectedAmount": "35000.00",
  "receivedAmount": "30000.00"
}
POST/v1/held-transfers/:id/acceptIn plan 035

Accept a held transfer

Credits the amount that arrived as a new charge of its own, with the usual fee. The original checkout is left as it was. Needs the refund permission, so it is done by a signed-in team member (Owner, Admin, Finance or Support); API keys cannot accept. A transfer that is no longer held is refused with 409.

Request Example
POST /v1/held-transfers/:id/accept
cURL · v1
curl -X POST "https://api.rafiqpay.net/v1/held-transfers/:id/accept" \
  -H "Authorization: Bearer rp_test_sk_kanzu_9f8a7b6c5d4e3f2a" \
  -H "Idempotency-Key: idem_accept-held-transfer_01"
Response (200 OK)
Response payload
{
  "heldTransfer": {
    "id": "htr_01J9KANZU7",
    "status": "accepted",
    "acceptedChargeId": "pay_01J9KANZU902"
  },
  "chargeId": "pay_01J9KANZU902"
}
POST/v1/held-transfers/:id/returnIn plan 035

Return a held transfer

Sends the full amount back to the customer, with no fee, to the account they give you. Only a team member signed in to the dashboard can do this, with the refund permission and a fresh code from their authenticator app; an API key is refused. If the payout fails, the money is held again. transfer.returned is sent when the money has gone. A transfer that is no longer held is refused with 409.

Request Parameters
ParameterTypeRequirementDescription
bankCodestringRequiredThe customer’s bank, from GET /v1/transfers/banks.
accountNumberstringRequiredThe customer’s 10-digit account number. The bank does not tell us which account the money came from, so ask the customer.
codestringRequiredA fresh code from your authenticator app.
Request Example
POST /v1/held-transfers/:id/return
cURL · v1
curl -X POST "https://api.rafiqpay.net/v1/held-transfers/:id/return" \
  -H "Authorization: Bearer rp_test_sk_kanzu_9f8a7b6c5d4e3f2a" \
  -H "Idempotency-Key: idem_return-held-transfer_01" \
  -H "Content-Type: application/json" \
  -d '{
  "bankCode": "058",
  "accountNumber": "0123456789",
  "code": "482910"
}'
Response (200 OK)
Response payload
{
  "heldTransfer": {
    "id": "htr_01J9KANZU7",
    "status": "returning",
    "returnPayoutId": "trf_01J9KANZU"
  },
  "payoutStatus": "processing"
}
Possible Error Responses
HTTP StatusError CodeCondition
400AUTHENTICATOR_CODE_INVALIDThe code did not match. Use the latest code in the authenticator app.
400AUTHENTICATOR_CODE_USEDThe code was already used. Wait for the next one.
403TWO_FACTOR_SETUP_REQUIREDThe team member has not set up two-step sign-in.

These events are dispatched to your webhook endpoints when actions occur on this resource: