Skip to content
API DocsDocs

Webhooks Quick Start

Set up a webhook endpoint to receive and verify payment callbacks in minutes

3 min readUpdated Mar 26, 2026

Set up a minimal webhook handler: respond 200 OK immediately, verify the signature, deduplicate, then process.

#Prerequisites

  • A publicly accessible HTTPS endpoint
  • Your merchant secret key (from the Exirom dashboard)

#Minimal Webhook Handler

const crypto = require('crypto');
const express = require('express');
const app = express();
 
app.post('/webhooks/payment', express.json(), async (req, res) => {
  // 1. Respond 200 immediately — prevents Exirom from retrying
  res.status(200).send('OK');
 
  // 2. Verify signature
  const received = req.headers['x-checksum'];
  const { paymentMethod } = req.query;
  const p = req.body;
 
  const raw = paymentMethod === 'apm'
    ? `${p.accountId}|${p.amount}|${p.currency}|${p.transactionId}`
    : `${p.mid}|${p.orderAmount}|${p.orderCurrency}|${p.transactionId}`;
 
  const expected = crypto
    .createHmac('sha256', process.env.MERCHANT_SECRET)
    .update(raw)
    .digest('base64');
 
  if (expected !== received) {
    console.error('Invalid signature — ignoring callback');
    return;
  }
 
  // 3. Deduplicate
  const key = `${p.transactionId}:${p.transactionStatus}`;
  const exists = await db.webhookLog.findUnique({ where: { key } });
  if (exists) return;
  await db.webhookLog.create({ data: { key } });
 
  // 4. Process
  if (p.transactionStatus === 'SUCCEED') {
    await fulfillOrder(p.transactionId);
  } else if (p.transactionStatus === 'FAILED') {
    await notifyCustomer(p.transactionId, p.declineCode);
  }
});

#What the Handler Does

StepWhy
Respond 200 firstExirom retries if it doesn't receive 200 within 10 seconds
Verify X-ChecksumPrevents forged callbacks from triggering fulfillment
Deduplicate on transactionId + transactionStatusExirom delivers at-least-once — the same callback may arrive twice
Check transactionStatusSUCCEED → fulfill; FAILED → notify customer; PENDING → wait for webhook

#Key Callback Fields

FieldDescription
transactionIdUnique transaction ID — use as your deduplication key
transactionStatusSUCCEED, FAILED, PENDING, REFUNDED
declineCodeSet when transactionStatus is FAILED — see Decline Codes Reference
paymentMethodQuery param appended by Exirom: card or apm

#Next Steps

Was this helpful?