Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.papp.sa/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are how Points tells your backend that something happened — an order was approved, a refund was issued, a shipping status changed. You register one or more HTTPS endpoints; Points posts JSON to them whenever an event fires. Webhooks are the source of truth for order state on your side. Poll-based reconciliation is a safety net, not the primary path.

When webhooks fire

EventFired when
approvedOrder completes and points are settled
authorizedFunds authorised (auth+capture PSPs)
capturedPreviously authorised funds captured
cancelledOrder cancelled
completedReplacing order completed via POST /orders/{uuid}/complete
refundedFull or partial refund processed
auto_refundedInternal automatic reversal performed by Points
shipping_status_updatedShipping status changed via POST /orders/{uuid}/status
Full payload schemas in Webhook events.

Delivery mechanics

  • Transport — HTTPS POST, Content-Type: application/json.
  • Timeout — 10 seconds per attempt.
  • Retries — up to 3 attempts on transient failure (non-2xx response, network error, timeout).
  • Delivery order — not guaranteed. Your handler must be idempotent (see below).
  • Headers:
HeaderValue
Content-Typeapplication/json
X-Webhook-SecretThe secret you registered with the webhook (use this to verify authenticity)
X-Webhook-EventEvent name (e.g. approved). Same value as event in the body.

Payload shape

Every event has the same envelope:
{
  "event": "approved",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_number": "REF-2024-001234",
    "order_number": "ORD-2024-0001",
    "type": "replacing",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "order_status": "approved",
    "metadata": { "source": "web" },
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:05:12Z"
  },
  "timestamp": "2026-04-18T10:05:12Z"
}
Some events include additional keys inside order (e.g. status + status_label for shipping_status_updated). See Webhook events for per-event fields.

Register a webhook

Use the API, or the dashboard (Settings → Webhooks). Example:
curl -X POST https://business.papp.sa/api/v1/webhooks \
  -H "x-api-key: $POINTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Notifications",
    "url":  "https://merchant.example.com/webhooks/points"
  }'
Response:
{
  "data": {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "merchant_id": 123,
    "name": "Order Notifications",
    "url": "https://merchant.example.com/webhooks/points",
    "secret": "abc123def456...",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:00:00Z"
  }
}
Store the secret securely — it is how you verify webhook authenticity (see below).
See Webhook management for listing, updating, and deleting webhooks.

Multiple endpoints

You can register more than one webhook per merchant. Common reasons:
  • Separate environments (staging vs production).
  • Separate downstream consumers (fulfilment, analytics, CRM).
  • A “dead-letter inspector” endpoint that only logs for audit.
Every registered webhook receives every event for your merchant. Filtering is your responsibility.

Handling webhooks

The recommended consumer pattern boils down to four rules:
  1. verify X-Webhook-Secret
  2. acknowledge quickly with 2xx
  3. enqueue work instead of processing inline
  4. deduplicate on (order.id, event)

Verifying the secret

Use a constant-time comparison so you don’t leak timing information.
import crypto from "node:crypto";

function verifySecret(received, expected) {
  const a = Buffer.from(received || "", "utf8");
  const b = Buffer.from(expected || "", "utf8");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

Fast-ack pattern

Your endpoint must respond 2xx within the 10-second timeout budget. Anything slower triggers a retry, which leads to duplicates. Do this:
app.post("/webhooks/points", async (req, res) => {
  if (!verifySecret(req.header("X-Webhook-Secret"), process.env.POINTS_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }

  await queue.publish("points.webhook", req.body); // enqueue
  return res.status(200).end();                    // ack immediately
});
Do not:
  • call slow third-party services before responding
  • send email synchronously
  • update ERP/WMS inline if it can block the response
Your worker pulls from the queue and does the real work (update DB, send email, notify WMS). If the worker fails, it can retry locally without Points re-delivering.

Idempotency strategy

Points may deliver the same (order.id, event) pair more than once — retry loops, backend replays, network blips. Design your handler so repeat deliveries are safe. Recommended database uniqueness rule:
create unique index points_webhook_event_dedupe
  on webhook_events (points_order_uuid, event);
Or in application code:
const existed = await db.webhookEvents.findOne({
  where: { orderUuid: payload.order.id, event: payload.event }
});
if (existed) return res.status(200).end(); // ack, already processed
If a duplicate arrives, return 200 and do nothing.

Status codes

Your responseMeaning
200 / 204Accepted
401 / 403Secret invalid
5xxTemporary processing failure; may trigger retry

Retry expectations

Current backend behavior:
  • each outbound request uses a 10 second timeout
  • the job retries up to 3 times on failure
Your side should therefore assume duplicates are normal.

Local development

Webhooks need a publicly reachable HTTPS URL. In local dev, tunnel your laptop to the internet: Register the tunnel URL as a webhook in the sandbox environment and trigger test events by calling the API against a sandbox order.

Operational tips

A sudden wave of secret mismatches often means the secret was rotated on one side only, or the endpoint was copied incorrectly.
Store the raw JSON and headers for a limited retention period in staging/production so support can reconcile difficult cases.
Never reuse production webhook secrets in staging or local development.
See Security for broader hardening guidance.

Next

Webhook events

Per-event payload schemas and sample bodies.

Webhook management

CRUD operations via API or dashboard.