A lightweight webhook router that receives callbacks from Xendit and forwards them to multiple applications. This solves Xendit's limitation of only allowing one webhook URL per user.
- ✅ Receives raw-body webhooks from Xendit
- ✅ Forwards webhooks to multiple target applications
- ✅ Ignores 404/5xx errors from target apps
- ✅ Always returns 200 to Xendit (prevents retries)
- ✅ Lightweight and database-free
- ✅ Deployed as Vercel Serverless Function
xendit-webhook-relay/
├── api/
│ └── xendit.js # Main webhook handler
├── package.json
├── vercel.json
└── README.md
pnpm installEdit api/xendit.js and update the TARGETS array with your application webhook URLs:
const TARGETS = [
"https://your-app-1.com/webhook/xendit",
"https://your-app-2.com/webhook/xendit",
"https://your-app-3.com/webhook/xendit",
];# Install Vercel CLI if you haven't
pnpm add -g vercel
# Deploy
pnpm run deployOr connect your GitHub repository to Vercel for automatic deployments.
Set your webhook URL in Xendit dashboard to:
https://your-vercel-domain.vercel.app/api/xendit
# Start local development server
pnpm run dev
# Test with curl
curl -X POST http://localhost:3000/api/xendit \
-H "Content-Type: application/json" \
-d '{"id":"test-invoice-123","status":"PAID"}'- Xendit sends a webhook to your Vercel function
- The function receives the raw payload
- Payload is forwarded to all target applications in parallel
- Function always returns 200 OK to Xendit (even if targets fail)
- Errors are logged but don't affect the response to Xendit
| Scenario | Router Behavior | What Xendit Sees |
|---|---|---|
| App 1 returns 200 | Forward succeeds | OK |
| App 2 returns 404 | Ignored | OK |
| App returns 5xx | Ignored | OK |
| All apps fail | Still returns 200 | OK |
- The router always returns 200 to Xendit to prevent retries
- Failed forwards to target apps are logged but don't block the response
- Target apps must handle duplicate webhooks gracefully
- Consider adding HMAC verification for production use
To verify Xendit webhooks with callback token:
const XENDIT_CALLBACK_TOKEN = process.env.XENDIT_CALLBACK_TOKEN;
// In handler function, before forwarding:
const callbackToken = req.headers['x-callback-token'];
if (callbackToken !== XENDIT_CALLBACK_TOKEN) {
console.error('Invalid callback token');
return res.status(200).send('OK'); // Still return 200
}MIT