Home

API documentation

Build integrations on top of your ETapri store. Sync orders to your ERP, push products from an external PIM, or receive real-time order events at your own endpoint.

v1
REST + JSON
HMAC-signed webhooks

Authentication

All requests require a per-store API key passed as a Bearer token. Generate one from your store's API keys page in the dashboard. Keys are shown once at creation — store them securely. Each key is scoped to a single store.

curl https://etapri.shop/api/public/v1/orders \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Base URL: https://etapri.shop/api/public/v1. All endpoints return JSON. Use HTTPS only — calls over HTTP are rejected.

Rate limits

60 requests per minute, per API key. Every response includes an X-RateLimit-Remaining header. When exceeded, you get 429 Rate limit exceeded — wait at least 60 seconds before retrying.

Errors

All errors return JSON in the shape:

{ "error": "Invalid or missing API key" }
400Invalid request (validation, bad JSON)
401Missing or invalid API key
404Resource not found
429Rate limit exceeded
500Server error — safe to retry

Orders

GET/api/public/v1/orders

List orders for your store, most recent first.

Query params

  • status — filter by status (e.g. paid, shipped)
  • from, to — ISO-8601 datetimes
  • limit — 1–100 (default 50), offset — 0–10000
curl "https://etapri.shop/api/public/v1/orders?status=paid&limit=20" \
  -H "Authorization: Bearer $ETAPRI_KEY"
GET/api/public/v1/orders/{orderNumber}

Fetch a single order with its line items.

curl https://etapri.shop/api/public/v1/orders/ORD-20260522-A7K3X9 \
  -H "Authorization: Bearer $ETAPRI_KEY"
PATCH/api/public/v1/orders/{orderNumber}

Update an order's status, tracking number, or notes. Status transitions to shipped / delivered / cancelled automatically stamp the corresponding timestamp and fire a webhook event.

Body (any of)

  • status — one of pending_whatsapp, pending_payment, paid, confirmed, packed, shipped, delivered, cancelled, refunded
  • tracking_number — string, max 120 chars
  • notes — string, max 2000 chars
curl -X PATCH https://etapri.shop/api/public/v1/orders/ORD-20260522-A7K3X9 \
  -H "Authorization: Bearer $ETAPRI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"shipped","tracking_number":"SMSA12345678"}'

Products

GET/api/public/v1/products

List products in your store (excludes deleted). Returns all language variants (name_ar, name_en, name_zh).

Query params

  • statusdraft, active, or archived
  • limit — 1–100, offset — 0–10000
curl "https://etapri.shop/api/public/v1/products?status=active" \
  -H "Authorization: Bearer $ETAPRI_KEY"

Webhooks

Register an HTTPS endpoint from your store's Webhooks page to receive real-time order events. Every delivery is signed with HMAC-SHA256 and retried with exponential backoff up to 6 times (1m → 5m → 30m → 2h → 6h → 24h) before being marked dead.

Event types
order.createdA new order was placed on the storefront
order.paidPayment succeeded (MyFatoorah, manual mark-paid, or PATCH status)
order.shippedStatus transitioned to shipped
order.deliveredStatus transitioned to delivered
order.cancelledStatus transitioned to cancelled

Verifying signatures

Each request includes an X-ETapri-Signature header in the form t=<unix_ts>, v1=<hex_hmac_sha256>. The signed string is t + "." + raw_body. Reject requests where t is older than ~5 minutes to prevent replay.

// Node.js / Express
import crypto from 'crypto';

app.post('/etapri-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.header('X-ETapri-Signature') || '';
  const parts = Object.fromEntries(header.split(',').map(s => s.trim().split('=')));
  const t = parts.t;
  const v1 = parts.v1;
  const body = req.body.toString('utf8');

  const expected = crypto
    .createHmac('sha256', process.env.ETAPRI_WEBHOOK_SECRET)
    .update(`${t}.${body}`)
    .digest('hex');

  if (!v1 || !crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
    return res.status(401).send('Stale timestamp');
  }

  const event = JSON.parse(body);
  // event.type, event.data
  res.status(200).send('ok');
});

Payload shape

{
  "type": "order.paid",
  "id": "evt_...",
  "created_at": "2026-05-22T10:32:18Z",
  "data": {
    "id": "...",
    "order_number": "ORD-20260522-A7K3X9",
    "status": "paid",
    "total": 149.50,
    "currency": "SAR",
    "customer_name": "...",
    "customer_email": "...",
    "...": "full order object"
  }
}

Your endpoint should respond with a 2xx status within 10 seconds. Any other response (or timeout) marks the delivery as failed and schedules a retry. View all attempts in your dashboard.