Getting started
Quickstart
Send one signed sale to UAT and watch it earn points. About twenty minutes, assuming you have your vendor slug.
1. Check you can reach the gateway
Before debugging signatures, prove the network path and TLS work.
bash
curl https://uat-connect.perx.mv/health
# → ok2. Get your credentials
- Your vendor slug and a UAT merchant + store come from your Perx contact.
- The connection secret is generated by the merchant: Perx dashboard → Settings → Integrations → Add connection. For UAT, your Perx contact will do this and send you the secret.
3. Send a sale
One POST, four headers, one signature. The customer phone below must belong to a Perx member on your UAT merchant — ask your contact for a test member.
const crypto = require('crypto');
const BASE = 'https://uat-connect.perx.mv';
const VENDOR = 'acmepos'; // your slug, from Perx
const STORE = '12345'; // the merchant's store id on your side
const SECRET = process.env.PERX_SECRET; // pasted by the merchant
async function sendSale() {
const payload = {
eventId: 'evt-' + Date.now(),
eventType: 'sale.completed',
occurredAt: new Date().toISOString(),
store: { id: STORE },
transaction: {
id: 'INV-2026-0042',
total: 45.0,
currency: 'MVR',
channel: 'pos',
customer: { type: 'phone', value: '+9607123456' },
items: [
{ name: 'Flat White', sku: 'SKU-COFFEE-001', quantity: 2, unitPrice: 5.5 },
{ name: 'Chicken Sandwich', sku: 'SKU-FOOD-014', quantity: 1, unitPrice: 34.0 },
],
},
};
// Sign the EXACT bytes you send. Serialise once, reuse the buffer.
const raw = Buffer.from(JSON.stringify(payload), 'utf8');
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac('sha256', SECRET)
.update(ts + '.').update(raw)
.digest('base64');
const res = await fetch(BASE + '/webhooks/generic', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Perx-Vendor': VENDOR,
'X-Perx-Store': STORE,
'X-Perx-Timestamp': ts,
'X-Perx-Signature': 'v1=' + sig,
},
body: raw,
});
console.log(res.status, await res.json());
}
sendSale();4. Read the response
A 200 with status: "processed" and a non-zero pointsEarned means the whole chain worked.
json
{
"status": "processed",
"membershipId": "0f6a1c9e-4b7d-4c2a-9f31-8a2d5e6b0c31",
"pointsEarned": 45,
"stampsUpdated": [
{
"rewardId": "b21e77a0-3c14-4e77-9a02-1d55f0b9c8e2",
"rewardTitle": "Coffee Club",
"current": 5,
"required": 5,
"completed": true
}
],
"warning": null
}Put this on the receipt
If you can print pointsEarned and a completed stamp card on the customer’s receipt, do it. It is the single highest-value thing you can build for the merchant’s customers, and it costs you one line.
5. Things that will happen next
- You get a
401. Almost always the signature. Paste your body and secret into the signature playground — it shows the exact value Perx expects and the most common reasons they differ. - You get
200butpointsEarned: 0and awarning. The customer is not a Perx member. That is a normal outcome, not an error — do not retry. - You send the same sale twice and get
status: "duplicate". Working as intended. Idempotency is ontransaction.id.
Where to go next
- Authentication — the signing scheme in full, and the replay window.
- Sale events — every field, and how customer matching works.
- Idempotency & retries — what to do when Perx is down.
- Certification — what you must demonstrate before production.