Webhooks
Webhooks allow your application to receive real-time notifications when events occur in MAES Platform.
How Webhooks Work
MAES Platform Your Server
│ │
│ 1. Event occurs │
│ (card enabled) │
│ │
│ 2. POST to your URL │
│ ─────────────────────────► │
│ with signed payload │
│ │
│ 3. 200 OK │
│ ◄───────────────────────── │
│ │
Webhook Events
| Event | Description |
|---|---|
card.created | New card synced from MAES |
card.activated | Card was activated |
card.enabled | Fuel authorizations enabled |
card.disabled | Fuel authorizations disabled |
card.synced | Card data synced from MAES |
sync.completed | Full sync completed successfully |
sync.failed | Sync operation failed |
test | Test event for verification |
Webhook Payload
All events follow this format:
{
"id": "evt_1a2b3c4d5e6f",
"event": "card.enabled",
"created_at": "2025-12-27T16:24:05.712Z",
"data": {
"card_id": "09c9861c-4c4b-411f-be85-c16ed7e26da4",
"card_number": "782521009000153700",
"license_plate": "AB-123-CD",
"driver": "John Doe",
"status": "active",
"auth_gasoline": true,
"auth_diesel": true,
"auth_lpg": true,
"auth_heating_oil": true,
"environment": "production"
}
}
Signature Verification
All webhook requests include a signature header:
X-Webhook-Signature: t=1703693400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Verification Steps
- Extract timestamp (
t) and signature (v1) from the header - Construct the signed payload:
{timestamp}.{raw_body} - Compute HMAC-SHA256 using your webhook secret
- Compare computed signature with
v1 - Optionally verify timestamp to prevent replay attacks
Node.js Example
const crypto = require('crypto');
function verifyWebhookSignature(payload, signatureHeader, secret) {
const [timestampPart, signaturePart] = signatureHeader.split(',');
const timestamp = timestampPart.replace('t=', '');
const signature = signaturePart.replace('v1=', '');
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex'),
);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log('Received event:', event.event);
// Handle the event
switch (event.event) {
case 'card.enabled':
// Handle card enabled
break;
case 'card.disabled':
// Handle card disabled
break;
}
res.status(200).send('OK');
});
Retry Logic
Failed deliveries are retried with exponential backoff:
| Attempt | Delay After Failure |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the delivery is marked as permanently failed.
Auto-Disable
Webhooks are automatically disabled after 10 consecutive failures. You can re-enable them from the dashboard.
Requirements
- HTTPS only — Webhook URLs must use HTTPS
- 200-299 response — Return a 2xx status to acknowledge receipt
- Respond quickly — Process asynchronously if needed (< 30s timeout)
Best Practices
- Always verify signatures — Don't trust unverified payloads
- Process asynchronously — Queue events for processing
- Handle duplicates — Events may be delivered more than once
- Log everything — Keep records for debugging
- Return 200 quickly — Avoid timeouts