# How payments work

Read this page before you go live. It covers the rules that every payin and payout follows.

## Statuses

A payment starts when Payd accepts your request and ends in `success` or `failed`.

| Status | Meaning | What to do |
| --- | --- | --- |
| `pending` | Payd accepted the request and is about to send it. | Wait. |
| `processing` | The network has the request. A customer may still be looking at the prompt. | Wait for the webhook, or check the status. |
| `success` | The money moved. | Fulfil the order or mark the payout as paid. |
| `failed` | The payment did not happen. The customer cancelled, the network declined, or the payout could not be delivered. | Show the customer what to do next. A failed payout is refunded to your balance. |

`success` and `failed` are final. A payment never moves out of them.

Card payments have one more step. A card payment reaches `success` when the customer pays, and Payd holds the money for a short period in case of a chargeback. When Payd releases it to your balance, you get a webhook with `status` `settled`. See [cards](./card-payments.md).

## References

Every payment has two references.

| Reference | Who sets it | Where you see it |
| --- | --- | --- |
| Payd reference, such as `9BD103350408eR` | Payd | `gateway_response.merchant_reference` in the response, `reference` in webhooks, `transaction_ref` in status |
| Your reference, such as `order-8842` | You, in `payment_config.reference` | `external_reference` in webhooks |

Payd references end in `eR` for collections and `eW` for payouts. Use the Payd reference to [check status](./transaction-status.md). Store both, side by side, so you can match a webhook to your order.

Payd does not check that your reference is unique. It is yours to keep unique.

## Amounts and currencies

- Send `amount` in the currency of the person paying or being paid: KES for M-Pesa in Kenya, UGX for Uganda, XOF for Senegal, and so on.
- Send whole numbers for mobile money. Networks do not take cents.
- Each network has a minimum and maximum. [Payment options by country](./network-discovery.md) lists them, and a [quote](./quotes.md) shows the fee for a given amount.
- Payd keeps your account balance in US dollars and converts each payment at the rate in force when it runs. A quote shows that rate.

For Côte d'Ivoire, Senegal, Benin and Togo (XOF) and Cameroon (XAF), several countries share one currency. Always send `country` so Payd knows where the money is going.

## Wallets

Your Payd account has two balances, which the [balances endpoint](./balances.md) shows:

| `wallet_type` | Balance |
| --- | --- |
| `fiat` (default) | Your main balance. |
| `crypto` | Your USD stablecoin balance. |

Payins credit, and payouts debit, the balance you name in `wallet_type`. Leave it out to use `fiat`.

## Reading responses

- Field names are `snake_case`. Payd also accepts `lowerCamelCase` in requests.
- Responses include every field, so empty values come back as `""`, `0` or `false`.
- Large whole numbers, such as page counts, come back as strings: `"count": "42"`. Parse them as numbers.
- Times are RFC 3339 strings in UTC, such as `2026-10-16T11:42:03Z`.
- Responses can carry more fields than these docs list. Ignore any field the docs do not describe: it may change without notice.
- Payd ignores fields it does not know in your request. A misspelt field name is dropped without an error, so check names against the docs.

## Retries and duplicates

Send an `idempotency_key` with every payin and payout. It is a string you choose, up to 100 characters, such as your order id or a UUID. It goes at the top level of the request body, next to `amount`:

```json
{
  "idempotency_key": "order-8842-attempt-1",
  "amount": 1500,
  "currency": "KES"
}
```

If Payd has already seen that key on your account:

- With the same request, Payd sends back the first response and does not charge or pay again. Resending after a timeout is safe.
- With a different request, Payd answers `409` `duplicate_conflict`: `this idempotency_key was already used for a different payment`. Use a new key for a new payment.
- If the first payment failed, Payd answers `409` `duplicate_conflict` with a message that it failed and its reference. Use a new key to try again.
- If the first request is still running, Payd answers `409` `duplicate_conflict` with a message that it is still being processed and its reference. Check its [status](./transaction-status.md) before you try again.

"The same request" means the same kind of payment (payin or payout), amount, currency, account number and wallet. Keys belong to your account. Never reuse one for another payment.

Without a key, Payd does not merge repeated requests. If you send the same payin twice, the customer gets two prompts. If you send the same payout twice, Payd pays twice. So without a key, never resend without checking first:

1. If you got a response with a `merchant_reference`, the request went through. Do not send it again. Check its [status](./transaction-status.md).
2. If the request timed out or you got a `5xx`, the payment may still exist. [Search your payment requests](./transaction-status.md#search-payment-requests) for the customer's phone number or account number from the last few minutes.
3. Send a new request only when you are sure the first one does not exist, and give it a new reference of your own.

Payd itself never retries a payin or payout on your behalf.

## Timeouts

Payd answers payin and payout requests within 30 seconds. Set your HTTP client timeout to at least 35 seconds. Most other endpoints answer within 15 seconds.

## Request ids

Every response has an `X-Request-Id` header, and error bodies repeat it as `request_id`. Log it. When you contact support about a request, send the request id with the time.

You can send your own `X-Request-Id` header, and Payd uses it for that request.

## Before your first payout

Payouts move money out of your balance, so Payd checks three things first:

- **Balance.** The balance must cover the amount and the fee. Otherwise you get `400` `insufficient_balance`.
- **Withdrawal PIN.** The person who owns the API key must have set a withdrawal PIN in the Payd app. API payouts do not ask for the PIN, but they fail until one is set.
- **Identity checks.** Payouts to some countries need the account holder's identity details to be complete in the Payd app. Payd tells you in the error message when this is missing.
