Webhooks
Receive real-time transaction status updates
Webhooks
Alya sends a POST request to your configured webhookUrl when a transaction status changes. Use the vendorReference in the payload to match the notification to your order.
Setup
Configure webhookUrl and webhookSecret on your API key. Webhooks are only sent for transactions created with your API key.
Request headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Alya-Event-Id | Unique event ID — use this to deduplicate |
X-Alya-Timestamp | ISO 8601 timestamp of when the webhook was sent |
X-Alya-Signature | sha256=<hmac> — verify this on every request |
Signature verification
Compute the expected signature and compare it against X-Alya-Signature:
signature = "sha256=" + HMAC-SHA256(webhookSecret, timestamp + "." + rawPayload)webhookSecret— secret configured on your API keytimestamp— exact value fromX-Alya-TimestampheaderrawPayload— raw request body as a string (do not parse before verifying)
Node.js example:
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-alya-timestamp'];
const receivedSig = req.headers['x-alya-signature'];
const rawBody = req.body.toString();
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSig))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
const { vendorReference, status } = event.data;
// match to your order using vendorReference
console.log(`Order ${vendorReference} → ${status}`);
res.sendStatus(200);
});Always respond with
2xxwithin 10 seconds. Alya retries up to 3 times on failure.
Event types
transaction.approved
Fires when the customer confirms the transaction. This is the primary signal that the order is validated — update your order to confirmed on this event.
{
"event": "transaction.approved",
"timestamp": "2026-06-26T10:00:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"vendorReference": "ORDER-001",
"orderReference": "ALY-20260626-001",
"status": "APPROVED",
"amount": 1500.00,
"currency": "MAD",
"customerPhone": "212612345678",
"merchantName": "Acme",
"storeName": "Main Store",
"createdAt": "2026-06-26T09:00:00Z"
}
}transaction.cancelled
Fires when the transaction is canceled by the merchant or system.
{
"event": "transaction.cancelled",
"timestamp": "2026-06-26T10:00:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"vendorReference": "ORDER-001",
"orderReference": "ALY-20260626-001",
"status": "CANCELED",
"amount": 1500.00,
"currency": "MAD",
"customerPhone": "212612345678",
"merchantName": "Acme",
"storeName": "Main Store",
"createdAt": "2026-06-26T09:00:00Z"
}
}transaction.expired
Fires when the customer did not confirm the transaction within the agreed expiry period. Same shape as transaction.cancelled with an additional expiredAt field.
{
"event": "transaction.expired",
"timestamp": "2026-06-26T11:00:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"vendorReference": "ORDER-001",
"orderReference": "ALY-20260626-001",
"status": "EXPIRED",
"amount": 1500.00,
"currency": "MAD",
"customerPhone": "212612345678",
"merchantName": "Acme",
"storeName": "Main Store",
"createdAt": "2026-06-26T09:00:00Z",
"expiredAt": "2026-06-26T11:00:00Z"
}
}Best practices
- Verify the signature on every request
- Use
X-Alya-Event-Idto deduplicate retries - Return
2xximmediately — process business logic asynchronously - Use HTTPS for your webhook URL