03API Reference
Every endpoint.
All endpoints under /api/v1/*. Base URL https://deripay.site.
Authentication
Every request requires three headers:
X-API-Key—<keyId>:<secret>X-Timestamp— ISO-8601, within ±300s of server clockX-Signature— hex HMAC-SHA256(secret, signing-payload)
Signing payload: ${timestamp}\n${METHOD}\n${path}\n${sha256_hex(body)}
POST
/api/v1/ratesGet current USD/KES deposit + withdraw rates.
Request body
json
{}Response
json
{
"success": true,
"rates": {
"depositRate": 132.5,
"withdrawRate": 128.0,
"normalRate": 130.0,
"minDepositKes": 50,
"maxDepositKes": 150000,
"minWithdrawUsd": 2,
"maxWithdrawUsd": 1000,
"marginPercent": 1.92
}
}POST
/api/v1/depositTrigger an M-Pesa STK Push to deposit USD into a Deriv account.
Required scopeThe userToken must have the payments scope. This applies to deposits AND withdrawals — Deripay runs both directions through Deriv's payment-agent APIs (paymentagent_transfer for deposits, paymentagent_withdraw for withdrawals). Tokens without the scope are rejected with DERIV_AUTH_FAILED before any STK push fires. For OAuth, the app's allowed-scopes in DevHub must include `payments`. For manual tokens, tick the Payments checkbox at app.deriv.com/account/api-token. See troubleshooting → missing-payments-scope.
appId bindingThe appId must match the Deriv app_id bound to your API key. Mismatched values are rejected with: "appId "X" does not match the Deriv app_id bound to this API key". Edit the key in the portal to change which app_id it's bound to.
userToken validationWe authorize your token against Deriv up-front. OAuth tokens minted under a different app_id, tokens missing the payments scope, or revoked/expired tokens are rejected before any STK push fires. Manual tokens authorize against any app_id — the payments scope is what matters.
Request body
json
{
"phoneNumber": "254712345678",
"usdAmount": 10,
"currency": "USD", // optional, defaults to USD
"loginid": "CR12345",
"userToken": "abcdef1234567890...", // must include payments scope
"appId": "52947", // MUST match the app_id bound to your API key
"accountTokens": { "CR12345": "abcdef..." }, // optional
"description": "Deposit for trading", // optional
"idempotencyKey": "client-generated-uuid" // optional
}Response
json
{
"success": true,
"transactionId": "deripay-tx-id", // poll this against /transactions/:id
"upstreamTransactionId": "edgepro-tx-id", // upstream Deripay's reference
"checkoutRequestId": "ws_CO_xxx", // Safaricom STK reference
"quote": {
"usdAmount": 10,
"amountKes": 1325,
"baseRate": 130,
"customerRate": 132.5,
"marginPercent": 1.92
},
"message": "STK push sent to your phone"
}POST
/api/v1/withdrawals/initiateStart a withdrawal — Deriv emails the user a verification code.
Required scopeSame as deposits — the userToken must have the payments scope. For OAuth, the Deriv app's allowed-scopes in DevHub must include `payments`. For manual tokens, tick the Payments checkbox when minting at app.deriv.com/account/api-token. See troubleshooting → missing-payments-scope.
appId bindingSame constraint as deposits — appId must match the Deriv app_id bound to your API key.
Request body
json
{
"phoneNumber": "254712345678",
"usdAmount": 10,
"currency": "USD", // optional, defaults to USD
"loginid": "CR12345",
"userToken": "abcdef...", // must include payments scope
"appId": "52947"
}Response
json
{
"success": true,
"transactionId": "deripay-tx-id",
"upstreamTransactionId": "edgepro-tx-id",
"message": "A Deriv verification code has been sent to the user's email...",
"quote": { "usdAmount": 10, "amountKes": 1280, "customerRate": 128 },
"paymentAgent": { "loginid": "CR1234567", "name": "DForge PA" }
}POST
/api/v1/withdrawals/confirmComplete the withdrawal with the verification code from email. A 200 response means Deriv accepted the code — the M-Pesa B2C payout still needs to settle. Always poll /transactions/:id until terminal.
Case-sensitive codeDeriv issues mixed-case alphanumeric codes (e.g. fyyBtoSf). Submit the code exactly as it appears in the email — preserve uppercase / lowercase, trim whitespace, never lowercase the input.
Token still validThe same Deriv userToken from initiate must still be valid when you call confirm. If it expires between the two calls (e.g. user paused for hours), confirm fails. For long-running flows, refresh the token before confirm.
Use transactionId, not upstreamTransactionIdPass the local transactionId returned from the initiate response. The upstreamTransactionId is exposed for reference only — it's not accepted as input here or by /transactions/:id.
Success ≠ completionA 200 typically returns transaction.status = "deriv_processing" / rawStatus = "WITHDRAWAL_B2C_PENDING". Polling /transactions/:id until "completed" is mandatory.
Request body
json
{
"transactionId": "deripay-tx-id",
"verificationCode": "fyyBtoSf" // CASE-SENSITIVE
}Response
json
{
"success": true,
"transactionId": "deripay-tx-id",
"upstreamTransactionId": "edgepro-tx-id",
"transaction": {
"status": "deriv_processing", // ← interim, not final
"rawStatus": "WITHDRAWAL_B2C_PENDING"
}
}GET
/api/v1/transactions/:idGet the current state of a transaction. Refreshes from upstream when not in a terminal state.
Request body
json
(no body — sign with empty body hash sha256(""))Response
json
{
"success": true,
"transaction": {
"id": "deripay-tx-id",
"type": "DEPOSIT",
"status": "completed",
"rawStatus": "DEPOSIT_COMPLETED",
"amounts": { "usd": 10, "kes": 1325, "rate": 132.5 },
"mpesa": { "checkoutRequestId": "...", "receiptNumber": "QXX..." },
"createdAt": "2026-05-06T12:00:00Z",
"updatedAt": "2026-05-06T12:01:23Z"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
| MISSING_API_KEY | 401 | X-API-Key header missing |
| MALFORMED_API_KEY | 401 | Key not in keyId:secret form |
| UNKNOWN_API_KEY | 401 | Key not found |
| BAD_SECRET | 401 | Secret does not match |
| BAD_SIGNATURE | 401 | HMAC signature does not verify |
| STALE_TIMESTAMP | 401 | Timestamp outside ±300s window |
| REVOKED_API_KEY | 401 | Key has been revoked |
| ORIGIN_NOT_ALLOWED | 403 | Strict mode: Origin doesn't match key's domain |
| DEVELOPER_SUSPENDED | 403 | Developer account suspended |
| RATE_LIMITED | 429 | Per-API-key rate limit exceeded |
| UPSTREAM_ERROR | 502 | Upstream Deripay returned error |
| UPSTREAM_TIMEOUT | 0 | Upstream Deripay timed out |