01Prerequisites
- An active YallaBuy store with an owner account (or integration management permission).
- A public HTTPS server — port 443 or 8443 only, with a valid TLS certificate matching the hostname. Internal and local addresses are rejected by the platform itself (see the security requirements page).
- A receiver that accepts POST with a JSON body: reads the RAW body before any parsing (required for signature verification), and responds with any 2xx within 10 seconds.
02Registering in the dashboard
- Open "Settings" then "Webhooks" (path /dashboard/settings/integrations/webhooks) and press "Create endpoint". This is the only registration mechanism — there is no public management API for webhooks.
- Fill in the fields: name (1–100 chars, unique within your store), URL (HTTPS only, port 443 or 8443, max 2048 chars, no user:pass@), and subscriptions (1–25 types from the 16-type subscribable catalog).
- Save and copy the signing secret immediately — it starts with whsec_ and is shown EXACTLY ONCE. If you lose it, rotate the secret from the same screen.
- The store limit is 10 endpoints — pause or delete one if you hit the cap before creating another.
03The test event and inspecting your first delivery
- Press "Send test event" on the endpoint — within about a minute a signed ping arrives, using the same headers and the exact same signature scheme as real events.
- Inspect the request in your server logs: the six webhook headers and the JSON body. The webhooks screen shows the delivery as DELIVERED with the HTTP status your server returned.
- Once the ping arrives and verifies, place a test order in your store — the first real business event arrives at the same speed.
If the delivery failed, open the delivery row in the delivery history: every attempt is listed with its outcome, HTTP status and a sanitized error summary. The outcome (TIMEOUT, NON_2XX, BLOCKED_URL, …) usually names the fix itself — the full table is on the troubleshooting page.
04A complete receiver skeleton
const express = require('express');
const crypto = require('node:crypto');
// Implement this adapter with a durable queue and a UNIQUE delivery-id key.
const { enqueueOnce } = require('./durable-queue');
const app = express();
const secrets = [
process.env.YALLABUY_WEBHOOK_SECRET,
process.env.YALLABUY_WEBHOOK_SECRET_PREV,
].filter(secret => typeof secret === 'string' && secret.length >= 32);
if (!secrets.length) throw new Error('Configure the webhook signing secret');
function verifyYallaBuyWebhook(rawBody, headers) {
const timestamp = String(headers['webhook-timestamp'] ?? '');
if (!/^\d{10,11}$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const ids = ['webhook-id', 'webhook-event-id', 'webhook-delivery-id']
.map(name => String(headers[name] ?? ''));
if (!ids.every(id => /^[A-Za-z0-9:_-]{1,200}$/.test(id))) return false;
const signature = String(headers['webhook-signature'] ?? '');
if (!/^v1=[a-f0-9]{64}$/.test(signature)) return false;
const message = [timestamp, ...ids, rawBody].join('.');
return secrets.some(secret => {
const expected = 'v1=' + crypto.createHmac('sha256', secret)
.update(message, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
});
}
app.post('/webhooks/yallabuy',
express.raw({ type: 'application/json', limit: '64kb' }),
async (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyYallaBuyWebhook(raw, req.headers)) return res.status(401).end();
let event;
try { event = JSON.parse(raw); }
catch { return res.status(400).end(); }
try {
// ACK only after durable persistence. Duplicate keys must be a no-op.
await enqueueOnce({ key: req.headers['webhook-delivery-id'], event });
return res.status(200).end();
} catch {
return res.status(503).end(); // No ACK: allow the sender to retry.
}
}
);
// Mount before any JSON body parser. Start the server in your app entry point.COMPLETE YOUR INTEGRATION
