This guide helps you migrate from the Xano webhook system to the new self-hosted webhook infrastructure that has been implemented in the DEAF-FIRST Platform.
┌─────────────────────────────────────────────────────────────────┐
│ DEAF-FIRST Platform │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Backend API (Express.js) │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Webhook Routes │ │ Incoming Webhook Routes │ │ │
│ │ │ /api/webhooks │ │ /api/incoming-webhooks │ │ │
│ │ └────────┬───────────┘ └──────────┬──────────────┘ │ │
│ │ │ │ │ │
│ │ └──────────┬───────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────▼───────────┐ │ │
│ │ │ Webhook Service │ │ │
│ │ │ - Registration │ │ │
│ │ │ - Event Delivery │ │ │
│ │ │ - Signature Verify │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ ▲
│ Sends webhooks to │ Receives webhooks from
│ your registered URLs │ Xano and other services
▼ │
┌─────────────────────┐ ┌─────────────────────┐
│ Your Application │ │ External Services │
│ - Receives events │ │ - Xano │
│ - Verifies sigs │ │ - Stripe │
│ - Processes data │ │ - Custom services │
└─────────────────────┘ └─────────────────────┘
Add webhook configuration to your .env file:
# Webhook Configuration
WEBHOOK_SECRET=your-webhook-secret-key-here
XANO_WEBHOOK_SECRET=your-xano-webhook-secret
Generate secure secrets:
openssl rand -hex 32 # For WEBHOOK_SECRET
openssl rand -hex 32 # For XANO_WEBHOOK_SECRET
cd /path/to/DEAF-FIRST-PLATFORM
npm run dev:backend
The webhook system will be available at http://localhost:3000/api/webhooks
If you had webhooks configured in Xano that pointed to your application, register them in the new system:
curl -X POST http://localhost:3000/api/webhooks \
-H "Content-Type: application/json" \
-d '{
"name": "Production App Webhook",
"url": "https://your-app.com/api/webhook",
"events": [
"user.created",
"user.updated",
"document.uploaded",
"auth.login"
],
"secret": "optional-custom-secret"
}'
Save the response! You’ll need the id and secret fields.
If you want to continue receiving events from Xano:
https://your-server.com/api/incoming-webhooks/xano
X-Webhook-Event: The event type (e.g., record.created)X-Webhook-Signature: Your XANO_WEBHOOK_SECRET (if implementing verification)Modify your application to handle webhooks from the new system:
// Your app received webhooks directly from Xano
app.post('/webhook/xano', (req, res) => {
const event = req.body;
// Process Xano event
processXanoEvent(event);
res.sendStatus(200);
});
const crypto = require('crypto');
// Your app receives webhooks from DEAF-FIRST Platform
app.post('/api/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const event = req.headers['x-webhook-event'];
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET; // From registration
// Verify signature
const expectedSig = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSig))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process verified webhook
console.log('Received event:', event);
console.log('Payload:', req.body);
res.status(200).json({ received: true });
});
Use the test endpoint to verify your webhook is working:
curl -X POST http://localhost:3000/api/webhooks/trigger \
-H "Content-Type: application/json" \
-d '{
"event": "user.created",
"data": {
"userId": "test-123",
"email": "test@example.com",
"name": "Test User"
}
}'
Check your application logs to confirm the webhook was received.
View webhook delivery history:
# Get your webhook ID
curl http://localhost:3000/api/webhooks
# View deliveries for a specific webhook
curl http://localhost:3000/api/webhooks/{webhook-id}/deliveries
Map your Xano events to the new event types:
| Xano Event | New Event Type | Description |
|---|---|---|
user_created |
user.created |
New user registration |
user_updated |
user.updated |
User profile updated |
user_deleted |
user.deleted |
User account deleted |
record_created |
(varies) | Map to appropriate domain event |
record_updated |
(varies) | Map to appropriate domain event |
Solution:
GET /api/webhooks/{id}Solution:
Solution:
Solution: Use ngrok or similar tool to expose local server:
ngrok http 3000
# Use the ngrok URL in your webhook configuration
{
"url": "https://your-app.com/webhook" // ✓ Good
"url": "http://your-app.com/webhook" // ✗ Bad (insecure)
}
// ✓ Good: Verify before processing
if (verifySignature(payload, signature, secret)) {
processWebhook(payload);
}
// ✗ Bad: Process without verification
processWebhook(payload); // Security risk!
// ✓ Good: Respond immediately, process async
app.post('/webhook', async (req, res) => {
res.status(200).json({ received: true });
await processWebhookAsync(req.body);
});
// ✗ Bad: Process synchronously
app.post('/webhook', async (req, res) => {
await longRunningProcess(req.body); // May timeout
res.status(200).json({ received: true });
});
// ✓ Good: Track processed events
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const eventId = req.headers['x-webhook-delivery'];
if (processedEvents.has(eventId)) {
return res.status(200).json({ received: true }); // Already processed
}
processWebhook(req.body);
processedEvents.add(eventId);
res.status(200).json({ received: true });
});
// ✓ Good: Log and monitor webhook health
app.post('/webhook', (req, res) => {
logger.info('Webhook received', {
event: req.headers['x-webhook-event'],
deliveryId: req.headers['x-webhook-delivery']
});
processWebhook(req.body);
res.status(200).json({ received: true });
});
If you need to rollback to Xano:
Before going live:
If you encounter issues:
GET /api/webhooks/{id}/deliveriesAfter successful migration:
Congratulations on successfully migrating your webhook system! 🎉