Skip to content

Refunds

You can refund a successful payment in full or in part. Every refund starts as a request that the Tinker Payments team reviews before any money moves. After approval, Tinker Payments sends the refund through the provider where it can, and you follow the request's status on the payment.

How a refund works

  1. You request a refund from the payment page in the dashboard, or through the API, with an amount, a reason and an optional note.
  2. Tinker Payments reviews the request and approves or rejects it. A rejection always gives a reason.
  3. On approval, Stripe, Paystack and Pesapal payments are refunded through the provider. M-Pesa has no refund API in Tinker Payments yet, so the Tinker Payments team refunds the customer directly and records the reference.
  4. The request becomes refunded once the refund is completed. Until then, treat the money as not returned.

Payments collected on Tinker Payments's provider accounts are refunded from your Tinker Payments wallet: the amount is reserved when the request is approved and released if the provider refuses it. Subscription payments collected by the provider itself are refunded in that provider's account; verify the payment in Tinker Payments afterwards to update it.

Who can request a refund

The account owner and team members whose role can manage billing (Admin and Finance). Other roles can see refunds but not request them.

Request a refund

Send an Idempotency-Key with every request and reuse it when you retry the same request, so a retry never creates a second refund. Leave out amount to refund everything that can still be refunded. reason is one of requested_by_customer, duplicate, fraudulent or other; note is optional, up to 500 characters.

POST https://api.tinkerpayments.com/v1/payments/{payment_id}/refund
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: 5d0c7a1e-9b8f-4a52-9a0e-3f1c2b7d6e40

{
  "amount": 750.00,
  "reason": "requested_by_customer",
  "note": "Customer returned one of two items"
}

HTTP 201 returns the new request (status: "pending_review") and the payment. Repeating the same request with the same key returns HTTP 200 with replayed: true.

HTTP 201: refund requested
{
  "success": true,
  "data": {
    "refund": {
      "id": "8b3f0d52-1c1e-4b7a-9d55-0a6c2e4f9b11",
      "payment_id": "{payment_id}",
      "amount": 750.00,
      "currency": "KES",
      "reason": "requested_by_customer",
      "status": "pending_review",
      "execution": "provider",
      "created_at": "2026-10-11T09:00:00Z"
    },
    "payment": { "id": "{payment_id}", "refunded_amount": 0, "refundable_amount": 1750.00 },
    "replayed": false
  }
}

Follow a refund

GET /payments/{payment_id} includes refunded_amount (money that actually reached the customer), refundable_amount, refund_capability and the latest refunds. GET /payments/{payment_id}/refunds returns the same refund fields on their own. A payment refunded in part has the status partially_refunded, and refunded once nothing is left to refund.

Refund request statuses
StatusMeaning
pending_reviewRequested. Waiting for Tinker Payments to review it. No money has moved.
approvedApproved. Tinker Payments is sending the refund to the provider, or, where the provider has no refund API (M-Pesa), the Tinker Payments team refunds the customer directly.
processingApproved and sent to the provider. Not yet confirmed. If the provider gives no clear answer, the amount stays held in your wallet until Tinker Payments confirms the outcome with the provider.
failedThe provider did not accept the refund, or accepted it and failed it later. No money reached the customer, nothing stays taken from your wallet, and failure_message says why. Tinker Payments follows up and either retries or rejects it.
rejectedTinker Payments rejected the request. rejection_reason says why. No money has moved.
refundedThe refund was completed. This is the only status that means the customer was refunded.

refund_capability.supported says whether a request can be made now. When it is false, unavailable_reason says why: not_successful, fully_refunded, refund_open (the rest of the payment is already in a request under review) or provider_managed.

Errors

  • VALIDATION_ERROR (400): details.fields names each field to fix, for example an amount above what can still be refunded.
  • NOT_REFUNDABLE (409): the payment can't be refunded any more; details.reason says why.
  • IDEMPOTENCY_KEY_REUSED (409): the key was already used for a different request.
  • REFUND_NOT_SUPPORTED (422): the payment was collected by its provider; refund it in the provider's account.

Timing and limits

Once a provider accepts a refund, it usually reaches the customer in 5–10 business days with Stripe and within 10 business days with Paystack; with Pesapal it depends on the customer's payment method. These are estimates, not guarantees. Tinker Payments keeps its platform fee on refunded payments. Voids (cancelling an authorisation before capture) are not supported.

Check payment-method support