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 clock
  • X-Signature — hex HMAC-SHA256(secret, signing-payload)

Signing payload: ${timestamp}\n${METHOD}\n${path}\n${sha256_hex(body)}

POST/api/v1/rates

Get 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/deposit

Trigger 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/initiate

Start 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/confirm

Complete 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/:id

Get 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

CodeHTTPDescription
MISSING_API_KEY401X-API-Key header missing
MALFORMED_API_KEY401Key not in keyId:secret form
UNKNOWN_API_KEY401Key not found
BAD_SECRET401Secret does not match
BAD_SIGNATURE401HMAC signature does not verify
STALE_TIMESTAMP401Timestamp outside ±300s window
REVOKED_API_KEY401Key has been revoked
ORIGIN_NOT_ALLOWED403Strict mode: Origin doesn't match key's domain
DEVELOPER_SUSPENDED403Developer account suspended
RATE_LIMITED429Per-API-key rate limit exceeded
UPSTREAM_ERROR502Upstream Deripay returned error
UPSTREAM_TIMEOUT0Upstream Deripay timed out