Wasal
Merchant API Reference • v1

Merchant Developer Documentation

Integrate Wasal with your store in minutes. Create Vodafone Cash and InstaPay payment requests and receive signed webhooks the second a transfer is confirmed.

Overview & Base URL

All Merchant API requests are made over HTTPS using JSON. When creating a payment, pass the amount in Egyptian Pounds (EGP). In responses and webhook payloads, amounts are returned in piasters (100 piasters = 1 EGP).

API Base URL

https://api.wasal-pay.com/v1

Authentication

Generate an API key from your Merchant Dashboard once your account is approved. Pass the key in the x-api-key header on every request. Keep your API keys secret on your server and never expose them in client-side code.

HTTP Header
x-api-key: wsl_live_9f8e7d6c5b4a3f2e1d0c...
POST/v1/payments

Create a Payment Request

Creates a new payment request tied to your store's order reference and returns a hosted checkout link (payUrl) where your customer can see your wallet details and upload their receipt.

FieldTypeRequiredDescription
amountnumberYesAmount in EGP (e.g. 750 = 750.00 EGP, stored as 75000 piasters)
method"vodafone_cash" | "instapay"YesTransfer method selected by the customer
referencestring (1..64)YesUnique order or invoice reference in your system
descriptionstringOptionalOptional description shown for your records
expiresInMinnumber (5..10080)OptionalExpiration window in minutes (default: 1440 = 24h)
Request (cURL)
curl -X POST https://api.wasal-pay.com/v1/payments \
  -H "x-api-key: wsl_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 750,
    "method": "vodafone_cash",
    "reference": "order-1042",
    "description": "Order #1042 - Cairo Store",
    "expiresInMin": 1440
  }'
Response (201 Created)
{
  "id": "b7e2f8a1-91d4-4c68-9f14-2c18a3918b22",
  "merchantId": "63f1e9c2-4411-4090-81c4-0b3c12345678",
  "publicToken": "a8f92e1b90c34d128e77f123456789ab",
  "reference": "order-1042",
  "amount": 75000,
  "method": "vodafone_cash",
  "status": "pending",
  "expiresAt": "2026-11-04T12:00:00.000Z",
  "payUrl": "http://localhost:3001/pay/a8f92e1b90c34d128e77f123456789ab"
}

Drop-in checkout button

Paste this on your site. Clicking the button creates a payment and sends the customer to the Wasal checkout. Keep the API key on your server in production. The button below is only for trying the same script.

Paste into your page

<script src="https://your-wasal-host/wasal.js" data-api="https://api.wasal-pay.com"></script>
<button
  data-wasal-pay
  data-key="YOUR_API_KEY"
  data-amount="750"
  data-method="vodafone_cash"
  data-reference="order-1042"
>
  Pay 750 EGP with Wasal
</button>

Prompt for your developer or coding agent

Add Wasal checkout to this site. Load wasal.js from the Wasal site and set data-api="https://api.wasal-pay.com". Add a button with data-wasal-pay, data-key set to the merchant API key, data-amount in EGP, data-method of vodafone_cash or instapay, and a unique data-reference. On click, POST /v1/payments and redirect the customer to payUrl. Do not build a second checkout page.

Try the same button

GET/v1/payments & /v1/payments/:id

List & Fetch Payments

Retrieve a list of recent payments (optionally filtered by status) or fetch a single payment by its unique ID.

List & Fetch Payments (cURL)
# List recent payments (optional status filter)
curl -X GET "https://api.wasal-pay.com/v1/payments?status=confirmed" \
  -H "x-api-key: wsl_live_your_api_key"

# Get a single payment by ID
curl -X GET "https://api.wasal-pay.com/v1/payments/b7e2f8a1-91d4-4c68-9f14-2c18a3918b22" \
  -H "x-api-key: wsl_live_your_api_key"
POST/v1/payments/:id/receipt

Direct Receipt Upload

If you build a custom checkout UI instead of redirecting to the hosted payUrl, you can upload the customer's receipt image directly from your backend using multipart/form-data.

Multipart Receipt Upload (cURL)
curl -X POST "https://api.wasal-pay.com/v1/payments/b7e2f8a1-91d4-4c68-9f14-2c18a3918b22/receipt" \
  -H "x-api-key: wsl_live_your_api_key" \
  -F "file=@/path/to/receipt-screenshot.jpg"

Payment Lifecycle Statuses

Every payment transitions through the following statuses from creation to verification:

pending

Payment created; waiting for customer receipt upload or SMS/notification match.

confirmed

Receipt matched with a verified message on the merchant's Android phone.

needs_review

Receipt uploaded, awaiting manual merchant review (e.g., delayed SMS or partial mismatch).

rejected

Manually rejected by the merchant.

expired

Payment expired before completion.

Webhooks & HMAC-SHA256 Verification

Whenever a payment status changes (such as automatic or manual confirmation), Wasal sends a POST request to your configured Webhook URL signed with your Webhook Secret in X-Wasal-Signature and the Unix timestamp in X-Wasal-Timestamp.

Webhook Request Example
POST https://yourstore.com/api/webhooks/wasal
Content-Type: application/json
X-Wasal-Signature: 84e7f902d1c48b20e3a591c22f8901d23b4567c89a0123b456789c0123456789
X-Wasal-Timestamp: 1793777520

{
  "event": "payment.confirmed",
  "paymentId": "b7e2f8a1-91d4-4c68-9f14-2c18a3918b22",
  "reference": "order-1042",
  "amount": 75000,
  "method": "vodafone_cash",
  "status": "confirmed",
  "confirmedBy": "auto"
}
Verifying Signatures (Node.js / TypeScript)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWasalWebhook(
  rawBody: string,
  signatureHeader: string,
  timestampHeader: string,
  webhookSecret: string,
): boolean {
  const expected = createHmac("sha256", webhookSecret)
    .update(`${timestampHeader}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureHeader, "utf8");
  return a.length === b.length && timingSafeEqual(a, b);
}