AlyaPay Documentation
API Integration

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

HeaderDescription
Content-Typeapplication/json
X-Alya-Event-IdUnique event ID — use this to deduplicate
X-Alya-TimestampISO 8601 timestamp of when the webhook was sent
X-Alya-Signaturesha256=<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 key
  • timestamp — exact value from X-Alya-Timestamp header
  • rawPayload — 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 2xx within 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-Id to deduplicate retries
  • Return 2xx immediately — process business logic asynchronously
  • Use HTTPS for your webhook URL