Webhook Routing Setup for Local Development¶
This guide explains how to set up webhook routing to receive SendGrid and WhatsApp webhooks on your local development environment.
Overview¶
When developing locally, you need to receive webhooks from external services (SendGrid for email, WhatsApp for messaging). Since these services can't directly reach your localhost, we use a webhook routing system that forwards requests to your local environment via a tunnel URL.
Prerequisites¶
- Tunnel Service - You need a service that exposes your local server to the internet. Choose one:
- ngrok (recommended): https://ngrok.com
- Tunnelmole: Simple alternative, install with
npm install -g tunnelmole - Cloudflare Tunnel: Free alternative with Cloudflare account
-
localtunnel: Open source option
npm install -g localtunnel -
Database Access - You need access to the staging database to add your routing entry
Step 1: Set Up Your Tunnel¶
Option A: Using ngrok¶
# Install ngrok (if not already installed)
# Download from https://ngrok.com/download
# Start ngrok tunnel for your local app (usually port 3000)
ngrok http 3000
# You'll see output like:
# Forwarding https://abc123.ngrok-free.app -> http://localhost:3000
Option B: Using Tunnelmole (tmole)¶
# Install tunnelmole
npm install -g tunnelmole
# Start tunnel for your local app
tmole 3000
# You'll see output like:
# https://xyz789.tunnelmole.net is forwarding to localhost:3000
Option C: Using Cloudflare Tunnel¶
# Install cloudflared
# See: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/
# Start tunnel
cloudflared tunnel --url http://localhost:3000
# You'll see output like:
# https://random-name.trycloudflare.com
Step 2: Add Your Route to the Database¶
Once you have your tunnel URL, add an entry to the dev_webhook_routes table in the staging database:
-- Connect to staging database via Supabase dashboard or your preferred SQL client
-- Complete insert example with all fields
INSERT INTO dev_webhook_routes (id, name, mobile_number, email, url, is_active, created_at, updated_at)
VALUES (
gen_random_uuid(), -- Auto-generate UUID
'Your Name', -- Your identifier
'+447700900123', -- Your WhatsApp test number (optional)
'yourname@example.com', -- Your SendGrid test email (optional)
'https://abc123.ngrok-free.app', -- Your tunnel URL (without trailing slash)
true, -- Active by default
NOW(), -- Current timestamp
NOW() -- Current timestamp
);
-- Simplified insert (recommended - uses defaults)
INSERT INTO dev_webhook_routes (name, mobile_number, email, url)
VALUES (
'Your Name', -- Your identifier
'+447700900123', -- Your WhatsApp test number (optional)
'yourname@example.com', -- Your SendGrid test email (optional)
'https://abc123.ngrok-free.app' -- Your tunnel URL (without trailing slash)
);
-- You can specify either mobile_number, email, or both depending on which webhooks you need
Examples:¶
For WhatsApp webhooks only:
INSERT INTO dev_webhook_routes (name, mobile_number, url)
VALUES ('John Dev', '+447700900123', 'https://abc123.ngrok-free.app');
For SendGrid webhooks only:
INSERT INTO dev_webhook_routes (name, email, url)
VALUES ('Jane Dev', 'jane@example.com', 'https://xyz789.tunnelmole.net');
For both webhooks:
INSERT INTO dev_webhook_routes (name, mobile_number, email, url)
VALUES ('Bob Dev', '+447700900456', 'bob@example.com', 'https://def456.trycloudflare.com');
Routing inbound email to your local aiHub (mirrors production)¶
In production, SendGrid posts inbound email to aiHub (which parses it,
uploads attachments to Supabase Storage, then calls the app's
/api/email/inbound/webhook). To exercise that same path locally, set the
optional aihub_url column on your route to a tunnel pointing at your local
aiHub (port 3001). When aihub_url is set, the router forwards SendGrid
emails to {aihub_url}/email/inbound instead of {url}/api/email/inbound/webhook.
One-time schema change on the staging DB (the column is additive and nullable, so existing routes keep working unchanged):
ALTER TABLE dev_webhook_routes
ADD COLUMN IF NOT EXISTS aihub_url text;
Point email at your local aiHub while WhatsApp / app routing stays on url:
-- Run a second tunnel for aiHub, e.g.:
-- cloudflared tunnel --url http://localhost:3001
UPDATE dev_webhook_routes
SET aihub_url = 'https://your-aihub-tunnel.trycloudflare.com'
WHERE email = 'yourname@example.com';
Note: aiHub on Vercel-style hosts is not involved here — your local aiHub runs as a normal Node process with no 4.5MB body cap. However, the router itself (hookway) runs on Vercel, so inbound emails larger than ~4.5MB are still rejected at hookway before they reach your tunnel. Local dev therefore covers small/medium emails end-to-end; test the large-attachment path in staging where SendGrid posts directly to the aiHub ALB.
Step 3: Test Your Setup¶
Testing WhatsApp Webhooks¶
- Send a WhatsApp message to the business number from your registered phone
- Check your local app logs - you should see the webhook hit
/api/sana/webhook - The webhook router will:
- Receive the webhook at the router app
- Extract your phone number from the payload
- Look up your URL in the database
- Forward the request to
{your-tunnel-url}/api/sana/webhook
Testing SendGrid Webhooks¶
SendGrid Inbound Parse now posts the full email (raw MIME + attachments, up to
~30MB) to the aiHub service rather than the Vercel app, because Vercel's
4.5MB function body limit rejects larger compliance emails. aiHub parses the
message, uploads attachments + raw email to Supabase Storage, then forwards a
small reference payload to the Vercel /api/email/inbound/webhook endpoint
(authenticated with the shared x-api-key).
For local development, point SendGrid (or your tunnel route) at your locally
running aiHub: {your-aihub-tunnel-url}/email/inbound. aiHub then calls your
local app's /api/email/inbound/webhook via VERCEL_API_BASE_URL.
- Send an email to your configured inbound email address
- Check your local aiHub logs - you should see the webhook hit
/email/inbound - aiHub verifies the SendGrid signature, parses + uploads, then calls the app's
/api/email/inbound/webhook, which queues it via QStash →/api/email/inbound/process
Managing Your Routes¶
View your active routes:¶
SELECT * FROM dev_webhook_routes WHERE name = 'Your Name';
Update your tunnel URL (when ngrok restarts):¶
UPDATE dev_webhook_routes
SET url = 'https://new-url.ngrok-free.app', updated_at = NOW()
WHERE name = 'Your Name';
Temporarily disable your route:¶
UPDATE dev_webhook_routes
SET is_active = false, updated_at = NOW()
WHERE name = 'Your Name';
Delete your route:¶
DELETE FROM dev_webhook_routes WHERE name = 'Your Name';
Important Notes¶
-
Tunnel URLs Change - Most free tunnel services generate new URLs on restart. Remember to update your database entry when this happens.
-
One Route Per Identifier - Each phone number and email can only have one active route. Coordinate with your team if sharing test numbers/emails.
-
Security - The webhook router uses the Supabase service role to bypass RLS. Never expose your tunnel URL publicly.
-
Local App Must Be Running - Ensure your local development server is running before testing webhooks.
-
Validation Happens Locally - The router doesn't validate webhooks. All signature verification and validation happens in your local app, just like in production.
Troubleshooting¶
Webhook not reaching local app:¶
- Check tunnel is running and URL is correct in database
- Verify
is_active = truefor your route - Check phone number/email matches exactly (including country code for phones)
- Look at router app logs (if accessible) for forwarding errors
Getting 404 errors:¶
- Ensure your local app is running on the expected port
- Verify the webhook endpoints exist in your local app:
/api/sana/webhookfor WhatsApp/api/email/inbound/webhookfor SendGrid
Tunnel keeps disconnecting:¶
- Consider using a paid ngrok account for stable URLs
- Try alternative tunnel services if one is unreliable
- Some corporate networks block tunnel services - try a different network
Alternative Setup (Without Database)¶
If you need a quick test without database setup, you can:
- Temporarily modify the webhook URL in the external service (WhatsApp/SendGrid)
- Point it directly to your tunnel URL
- Remember to change it back! This affects all developers
However, the database routing approach is strongly recommended for team development.