Tell why a transfer is stuck

Tell a transfer that is still moving from one that is paused, under review or failed, and take the one action that applies without sending the money twice.

Use this recipe to tell a transfer that is still moving from one that is paused, under review or failed. You then take the one action that applies, and you do not send the money twice. It is for a developer at a remittance app whose user says "my 300 USD to Ghana has said processing for two days".

Status. Available.

Money example

Kofi's 300 USD payout can be in one of four states, and each needs a different answer.

  1. Gravv is still waiting on the bank or the mobile-money operator. Wait.
  2. It is paused because sender details are missing. Send the details and resume it, and the transfer continues.
  3. It is under review. The money may already have left, so do not send it again. Contact support.
  4. It failed, and 300 USD comes back as a refund row. Fix the cause and send a new transfer.

Before you start

You have the transaction_id and the reference from POST /v1/transfer.

Find out what is required

Read the transaction row first, then the latest webhook.

Row status remark or webhook Meaning What to do
pending No remark. transfer.status.pending events keep arriving Moving. Gravv is waiting on a bank, an operator or the chain. A transfer does not fail because of time alone Wait
paused transfer.requires_information, with reason and limit_details.requirements[] Waiting on you for a limit, screening, sender details or a document Send what requirements lists to POST /v1/transfer/{reference}/resume before expires_at
waiting_approval None Created in the Dashboard and waiting for an approver. Transfers sent with an API key never wait for approval Approve it in the Dashboard
pending Transfer is under review. Please contact support before trying again. The webhook may say transfer.status.failed Under review. The money may already have left Do not send again. Contact support with the reference
failed A remark such as The recipient's account details are invalid. Your transfer has been refunded., and a transfer_refund row Failed and refunded Fix the cause, then send a new transfer with a new client_reference
failed transfer.status.failed with reason limit_pause_expired, supporting_document_expired or sender_details_expired (or _cancelled) A pause ran out or was cancelled Send a new transfer
completed Then transfer.status.returned Paid, then returned by the bank. A new incoming row appears Check the payee's details before sending again

Steps

1. Read the row

curl --request GET \
 --url https://api.gravv.xyz/v1/transactions/29a3b2e9-5d65-4441-9588-42dea2bc372f \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/transactions/29a3b2e9-5d65-4441-9588-42dea2bc372f", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/transactions/29a3b2e9-5d65-4441-9588-42dea2bc372f",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
{
  "data": {
    "id": "29a3b2e9-5d65-4441-9588-42dea2bc372f",
    "amount": "300",
    "date_created": "2026-10-11T09:12:03.7Z",
    "direction": "out",
    "transaction_type": "global_remittance",
    "reference": "7412b293-4729-4739-a14f-f3d719db3ad0",
    "channel": "mobile_money",
    "status": "pending",
    "remark": "Transfer is under review. Please contact support before trying again.",
    "client_reference": "kofi-gh-0042",
    "currency": "USD"
  },
  "error": null
}

Here the transfer is under review. Stop, and contact support with the reference 7412b293-4729-4739-a14f-f3d719db3ad0.

2. Read the latest event

List the transfer's events, then open the newest with GET /v1/webhooks/event/{id} and read payload.event_type, payload.event_data.reason and payload.event_data.limit_details.

curl --request GET \
 --url 'https://api.gravv.xyz/v1/webhooks/history?transaction_id=29a3b2e9-5d65-4441-9588-42dea2bc372f&event_type=transfer' \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/webhooks/history?transaction_id=29a3b2e9-5d65-4441-9588-42dea2bc372f&event_type=transfer", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/webhooks/history?transaction_id=29a3b2e9-5d65-4441-9588-42dea2bc372f&event_type=transfer",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

3. If it is paused, answer the pause

Use Check what a sender can still send to see how much the sender has left. Then resume the transfer with only the fields requirements asks for and a new Idempotency-Key. Get a stuck remittance moving shows each kind of pause and its resume body.

4. If it failed, confirm the refund

List the customer's transfer_refund rows.

curl --request GET \
 --url 'https://api.gravv.xyz/v1/transactions?customer_id=5be6128e-18c2-4797-a142-ea7d17be3111&transaction_type=transfer_refund' \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/transactions?customer_id=5be6128e-18c2-4797-a142-ea7d17be3111&transaction_type=transfer_refund", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/transactions?customer_id=5be6128e-18c2-4797-a142-ea7d17be3111&transaction_type=transfer_refund",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

Match the refund row to the transfer by reference.

What can go wrong

Response Why Fix
The webhook says transfer.status.failed, but the row is pending Gravv sends transfer.status.failed for a transfer under review Trust the row. The transfer is under review, not failed. Do not send it again
The webhook says transfer.status.completed, but the row is still pending The row's last update was delayed Check again later. If it stays pending, report the reference to support
410 ERROR_CODE_TRANSFER_PAUSE_EXPIRED on a resume The resume came after expires_at Send a new transfer
200 with "resumed": false and new requirements The transfer is still over the limit after your answer Supply what the new requirements lists
A resume replays an earlier answer You reused an earlier attempt's Idempotency-Key Use a new key for every attempt
404 transfer workflow not found on a resume You sent the transaction_id Use the reference
400 this transfer is not paused The transfer is moving, under review or final Use the table above
POST /v1/transfer returned 200 and you treated the transfer as sent A 200 can carry transfer_status paused Read transfer_status on every response
A payout went out twice after a timeout You retried the transfer with a new Idempotency-Key, or without a client_reference Retry a timeout with the same key and the same client_reference

Webhooks you will receive

  • transfer.status.pending, many times while the transfer moves.
  • transfer.requires_information for a pause, with limit_details.
  • transfer.resumed, with reason resumed or released.
  • transfer.status.completed.
  • transfer.status.failed, with a reason when a pause ended.
  • transfer.status.returned.

Testing in sandbox

A transfer above your sandbox limits policy produces a pause when the policy is in enforce mode. You cannot force a failed crypto transfer or a review from the API. Both are Not yet available in sandbox.

For example, a 5 USD sandbox mobile-money payout went to review. Its webhook was transfer.status.failed with no reason, and the row stayed pending with remark null. In sandbox, a pending row whose latest webhook says failed is the under-review case, even without the remark.

Reference

Was this page helpful?