Receive real-time event notifications from the ElevatedPOS platform. When an event occurs, ElevatedPOS sends an HTTP POST request to your configured endpoint with a signed JSON payload.
Subscribe
Register one or more HTTPS endpoints in your integration dashboard. Choose which event types to subscribe to.
Receive
ElevatedPOS makes an HTTP POST to your endpoint within seconds of an event occurring, with a JSON payload.
Verify & Process
Validate the HMAC-SHA256 signature before processing. Return 2xx within 30 seconds to acknowledge receipt.
Every webhook delivery includes an X-ElevatedPOS-Signature header containing an HMAC-SHA256 digest of the raw request body, keyed with your webhook secret:
X-ElevatedPOS-Signature: sha256=<hex-digest> # Computed as: HMAC-SHA256(key=WEBHOOK_SECRET, message=rawRequestBody)
crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing-based attacks.The header format is sha256=<hex-digest>. Compute HMAC-SHA256(rawBody, webhookSecret), prepend sha256=, then compare with a constant-time equality function.
import crypto from 'crypto';
export function verifyWebhookSignature(
rawBody: Buffer,
signature: string,
secret: string
): boolean {
const hmac = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const expected = `sha256=${hmac}`;
return crypto.timingSafeEqual(
Buffer.from(expected, 'utf8'),
Buffer.from(signature, 'utf8')
);
}
// Express.js usage
app.post('/webhooks/elevatedpos', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-elevatedpos-signature'] as string;
if (!verifyWebhookSignature(req.body, sig, process.env.ELEVATEDPOS_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
// Enqueue for async processing
queue.push(event);
res.sendStatus(200);
});import hmac, hashlib
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# Flask usage
@app.route('/webhooks/elevatedpos', methods=['POST'])
def elevatedpos_webhook():
sig = request.headers.get('X-ElevatedPOS-Signature', '')
if not verify_webhook_signature(request.get_data(), sig, ELEVATEDPOS_WEBHOOK_SECRET):
return jsonify(error='Invalid signature'), 401
event = request.get_json()
task_queue.enqueue(process_event, event)
return '', 200<?php
function verifyWebhookSignature(string $rawBody, string $signature, string $secret): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}
$rawBody = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_ELEVATEDPOS_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($rawBody, $sig, getenv('ELEVATEDPOS_WEBHOOK_SECRET'))) {
http_response_code(401);
die(json_encode(['error' => 'Invalid signature']));
}
$event = json_decode($rawBody, true);
// Dispatch to background worker
dispatch_job('process_webhook', $event);
http_response_code(200);All webhook deliveries share this outer envelope:
{
"id": "evt_01HXXXXXXXXXXXXXXXX",
"event": "order.created",
"orgId": "org_uuid",
"timestamp": "2024-09-15T10:30:00.000Z",
"apiVersion": "2024-09-01",
"data": {
// Event-specific payload shown in the catalog below
}
}The id field is a unique, stable identifier for each delivery attempt. Use it for idempotency — if you receive the same id twice, the second delivery is a retry and can be safely ignored after the first was processed successfully.
17 events across 5 services. Click any row to expand its sample payload.
| Event | Service | Description |
|---|---|---|
order.created | orders | A new order was placed at any location. |
order.completed | orders | Order was marked as fully complete. |
order.refunded | orders | A full or partial refund was processed. |
payment.captured | payments | Payment was successfully captured. |
payment.failed | payments | Payment attempt was declined or errored. |
customer.created | customers | New customer profile was created. |
customer.updated | customers | Customer profile fields were updated. |
inventory.low_stock | inventory | Product stock fell below the reorder point. |
inventory.stockout | inventory | Product stock reached zero. |
loyalty.points_earned | loyalty | Points were added to a member account. |
loyalty.tier_changed | loyalty | Member moved to a new loyalty tier. |
layby.created | orders | A new lay-by was opened for a customer. |
layby.payment_received | orders | A payment was received against a lay-by. |
layby.completed | orders | Lay-by was fully paid off and goods collected. |
layby.cancelled | orders | Lay-by was cancelled and deposit refunded. |
gift_card.issued | orders | A new gift card was issued. |
gift_card.redeemed | orders | A gift card was used as payment. |
Webhooks are retried when your endpoint returns a non-2xx status code or times out (30-second timeout per attempt). ElevatedPOS makes 3 total delivery attempts with the following schedule:
After all 3 attempts fail
The event is marked as failed.
Failed events are visible in your integration dashboard for 72 hours and can be manually replayed.
The final retry delay (30 minutes) is not used in this 3-attempt schedule. After attempt 3 fails the event enters the failed state immediately.
Endpoint Suspension
If an endpoint accumulates 10 consecutive delivery failures, ElevatedPOS will automatically suspend webhook delivery to that endpoint to protect platform throughput. You will receive an email notification when suspension occurs. Re-enable the endpoint from your integration dashboard after resolving the issue.
Use the integrations service to fire a test payload to your endpoint:
curl -X POST https://api.elevatedpos.com.au/api/v1/integrations/{integrationId}/webhooks/test \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"event": "order.created",
"webhookId": "wh_01HXXXXXXXXXXXXXXXX"
}'Test deliveries use synthetic data. Signature verification works identically to live events.
Always verify signatures
Never process a webhook payload without first verifying the HMAC-SHA256 signature. This ensures the request originated from ElevatedPOS and the body was not tampered with in transit.
Return 200 quickly
Your endpoint should return a 2xx response within 30 seconds. Move heavy processing to a background queue. If ElevatedPOS receives a timeout, it will retry the event.
Process events asynchronously
Acknowledge receipt immediately with a 200 response, then process the event in a background worker or queue. This prevents timeouts and decouples delivery from processing.
Handle duplicate deliveries idempotently
The same event may be delivered more than once during retries. Use the event's id field to deduplicate and ensure your handler is idempotent.
Subscribe only to what you need
Select only the event types relevant to your integration. This reduces payload volume and avoids unnecessary processing.
Monitor delivery health
Review the webhook delivery log in your integration dashboard regularly. Set up alerts for elevated failure rates before they accumulate toward the 10-failure suspension threshold.
Rotate secrets periodically
Webhook secrets should be rotated every 90 days. ElevatedPOS supports overlapping secrets during rotation — your old secret remains valid for 24 hours after a new one is set.
Use HTTPS endpoints only
ElevatedPOS only delivers to HTTPS endpoints with a valid TLS certificate. HTTP endpoints are rejected to prevent data exposure.