Skip to content

Withdrawals

How it works

A withdrawal converts USDC from the user's Regini account into KES paid out directly to their M-Pesa. The flow:

  1. Quote (recommended): fetch a rate quote for the USDC amount. The response shows the exact KES the user will receive and the fee. The quote is locked for 10 minutes.
  2. Initiate: submit the withdrawal with the rate_id. Regini debits the USDC from the account immediately and queues the M-Pesa payout. The API responds immediately; the withdrawal is now in-flight.
  3. Payout: Regini instructs a B2C M-Pesa transfer to the phone number stored on the account.
  4. Webhook: once M-Pesa confirms the transfer, a withdrawal.completed webhook fires.

Only one withdrawal can be in-flight per account at a time. A second request while one is pending is rejected with CONCURRENT_WITHDRAWAL. The payout goes to the phone stored on the account. You do not supply it per-request.


Step 1: Get a rate quote

You can quote by USDC amount or by KES amount, whichever your UX collects from the user.

By USDC:

GET /accounts/{account_id}/withdrawal-quote?amount_usdc=38

By KES (user enters how much KES they want to receive):

GET /accounts/{account_id}/withdrawal-quote?amount_kes=5000

Provide either amount_usdc or amount_kes, not both. The response is the same either way:

{
  "rate_id": "def456-...",
  "amount_usdc": 38.0,
  "fiat_amount_kes": 4970.0,
  "fee_usdc": 0.38,
  "fee_kes": 50.0,
  "exchange_rate": 130.79
}

exchange_rate is the effective KES/USDC rate with the FX spread already applied, this is the rate shown to the user. amount_usdc in the response is always the USDC that will be debited regardless of which input type you used. Pass the returned rate_id to confirm.

Step 2: Confirm the withdrawal

POST /accounts/{account_id}/withdrawals

{
  "rate_id": "def456-...",
  "reference": "optional-your-ref"
}

Idempotency-Key header required.

Response:

{
  "status": "success",
  "message": "Withdrawal initiated",
  "transaction_id": "tx-uuid-..."
}

Regini sends the KES payout to the user's M-Pesa. Once confirmed, a withdrawal.completed webhook fires.


Option B: Auto-confirm at live rate

Skip the quote step and execute immediately. Provide either amount_usdc or amount_kes (not both).

By USDC:

{
  "amount_usdc": 38,
  "auto_confirm_rate": true
}

By KES:

{
  "amount_kes": 5000,
  "auto_confirm_rate": true
}


Request fields

Field Required Description
amount_usdc Conditional USDC to debit. Required unless amount_kes or rate_id is provided.
amount_kes Conditional KES the user wants to receive. The USDC debit is back-calculated from the live rate.
rate_id Conditional Rate quote from GET .../withdrawal-quote. Required when auto_confirm_rate is false.
auto_confirm_rate No Default false. When true, executes at the live rate without a prior quote.
reference No Your own reference string (max 255 chars). Returned in the webhook and transaction history.

Limits

Limit Value
Minimum per transaction KES 100
Maximum per transaction KES 250,000
Daily (rolling 24 hours) KES 500,000 per account
Weekly (rolling 7 days) KES 2,000,000 per account

Deposit and withdrawal volumes are tracked independently, a KES 500,000 deposit does not affect your withdrawal allowance. Windows are rolling, not calendar-reset. Failed transactions are excluded from volume counts. Breaching a limit returns VELOCITY_LIMIT_EXCEEDED.


One withdrawal at a time

Only one withdrawal can be in-flight per account at a time. A second request while one is pending returns CONCURRENT_WITHDRAWAL.