Developer API documentation

Connect your own website to AshalNow: receive orders from your site directly into the same branch order list, and read products, inventory, and branches live. Keys are issued by the AshalNow team — contact us to get one for your account.

Quick start

All requests are sent to https://www.ashalnow.com/api/v1, and every request must carry this authorization header:

Authorization: Bearer <keyId>:<secret>

If your key has allowed domains configured, the Origin header must match one of them or the request is rejected with a 403. Every successful response has this shape:

{ "success": true, "data": ..., "meta": { "timestamp": "..." } }

And any failure has this shape:

{ "success": false, "error": { "code": "NOT_FOUND", "message": "Product not found" } }

Rate limits

Every key has a default limit of 60 requests/minute and 5000 requests/day (adjustable by our team as you need). Exceeding it returns 429 RATE_LIMITED.

Endpoints

GET/productsRequires permission: products:read

List of products. Optional filters: page, limit, category, search, inStock, minPrice, maxPrice, sort (name|-name|price|-price|newest).

curl "https://www.ashalnow.com/api/v1/products?inStock=true&limit=20" \
  -H "Authorization: Bearer ak_xxx:xxxxxx"

{
  "success": true,
  "data": [
    { "id": "...", "name": "Cola 330ml", "barcode": "...", "categoryName": "Drinks",
      "sellingPrice": 15, "quantity": 42, "imageUrl": null }
  ],
  "meta": { "page": 1, "limit": 20, "total": 118, "timestamp": "2026-07-25T10:00:00.000Z" }
}
GET/products/:idRequires permission: products:read

Data for a single product.

curl https://www.ashalnow.com/api/v1/products/PRODUCT_ID \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
GET/products/:id/stockRequires permission: inventory:read

Stock quantity for a specific product. Add ?branchId=... for a single branch's quantity, otherwise the total across all branches is returned.

curl "https://www.ashalnow.com/api/v1/products/PRODUCT_ID/stock?branchId=BRANCH_ID" \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
GET/categoriesRequires permission: products:read

All categories.

curl https://www.ashalnow.com/api/v1/categories \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
GET/branchesRequires permission: branches:read

Active branches only. If the store has no branches, the list comes back empty — an order in that case needs neither branchId nor area. serviceAreas are the same areas used to auto-determine the branch when creating an order with customer.area.

curl https://www.ashalnow.com/api/v1/branches \
  -H "Authorization: Bearer ak_xxx:xxxxxx"

{ "success": true, "data": [
  { "id": "...", "name": "Nasr City branch", "address": "...", "phone": "...",
    "acceptsPickup": true, "acceptsDelivery": true,
    "serviceAreas": ["Nasr City", "Fifth Settlement"] }
] }
GET/inventoryRequires permission: inventory:read

Stock quantity for all products at once. Add ?branchId=... for a single branch's quantities.

curl "https://www.ashalnow.com/api/v1/inventory?branchId=BRANCH_ID" \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
GET/inventory/:productIdRequires permission: inventory:read

Same logic as /products/:id/stock — an equivalent alternative.

curl https://www.ashalnow.com/api/v1/inventory/PRODUCT_ID \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
POST/ordersRequires permission: orders:write

Create a new order from your site. The customer is matched automatically by phone number (updated if it already exists, created as a new record if not). Quantities are checked live against available stock but only deducted once the order is fulfilled at the point of sale. If the store has branches, you need one of: branchId (if you already know the branch), or customer.area (the branch is auto-determined by whichever branch covers that area — the order is rejected with a clear message if no branch covers it, or more than one does).

curl -X POST https://www.ashalnow.com/api/v1/orders \
  -H "Authorization: Bearer ak_xxx:xxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "name": "Ahmed Mohamed", "phone": "01012345678",
      "address": "6th of October, District 1", "area": "Nasr City"
    },
    "items": [ { "productId": "PRODUCT_ID", "quantity": 2 } ],
    "deliveryMethod": "delivery",
    "notes": "Please call before delivery"
  }'
  // branchId is optional here — if the store has branches it's determined automatically from "area"

{ "success": true, "data": {
  "id": "...", "status": "pending",
  "customer": { "id": "...", "name": "Ahmed Mohamed", "phone": "01012345678" },
  "items": [ { "productId": "...", "productName": "...", "quantity": 2 } ],
  "branchId": "...", "deliveryMethod": "delivery", "createdAt": "..."
} }
GET/ordersRequires permission: orders:read

Only orders created by this key (orders are never shared between different keys). Filters: page, limit, status.

curl "https://www.ashalnow.com/api/v1/orders?status=pending" \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
GET/orders/:idRequires permission: orders:read

Details of a single order created with this same key, including its current status (pending/fulfilled/cancelled).

curl https://www.ashalnow.com/api/v1/orders/ORDER_ID \
  -H "Authorization: Bearer ak_xxx:xxxxxx"
POST/webhooks/test

Sends a synthetic test.ping event to the webhook URL registered on your key — to confirm the signature and receiving side work before relying on them.

curl -X POST https://www.ashalnow.com/api/v1/webhooks/test \
  -H "Authorization: Bearer ak_xxx:xxxxxx"

Error codes

CodeHTTPMeaning
UNAUTHORIZED401The key is invalid, or the format is wrong (must be Authorization: Bearer keyId:secret).
RATE_LIMITED429You exceeded this key's per-minute or per-day request limit.
NOT_FOUND404The path or resource (product/order) does not exist.
ERROR400/403/500A validation error, a missing permission, or an internal error — the message field explains the cause in detail.

Webhooks

If you registered a webhook URL on your key, we'll send a POST for every one of these events:

  • order.created
  • order.delivered
  • order.cancelled
  • inventory.low
  • inventory.out
  • product.updated
  • product.disabled

Payload shape:

{
  "event": "order.created",
  "apiVersion": "v1",
  "timestamp": "2026-07-25T10:00:00.000Z",
  "clientId": "ak_xxx",
  "data": { ... }
}

Every request carries the header X-AshalNow-Signature in the form sha256=<hex> — an HMAC-SHA256 of the raw body text (before any JSON.parse) using your key's webhook secret. Verify the signature before trusting any event:

const crypto = require("crypto");
const expected = "sha256=" + crypto
  .createHmac("sha256", webhookSecret)
  .update(rawBody) // raw string, not parsed JSON
  .digest("hex");
const valid = expected === receivedSignatureHeader;

If your server returns anything other than 2xx, or doesn't respond within 10 seconds, we retry automatically at increasing intervals (1 minute, 5 minutes, 30 minutes, 2 hours, 24 hours) before marking delivery as permanently failed.

Ready-made SDK (JavaScript / Node.js)

A lightweight client with no external dependencies — use it from your own server (not from the customer's browser, so your secret never leaks).

Download ashalnow.js
const AshalNow = require("./ashalnow.js");
const client = new AshalNow({ keyId: "ak_xxx", secret: "xxxxxx" });

const products = await client.listProducts({ inStock: true });

const order = await client.createOrder({
  customer: { name: "Ahmed Mohamed", phone: "01012345678", address: "..." },
  items: [{ productId: "PRODUCT_ID", quantity: 2 }],
  deliveryMethod: "delivery",
});