Express relay server that receives Meta (Messenger + Instagram) webhook requests, maps them to downstream URLs using a static JSON configuration, and forwards the request while mirroring the downstream response back to Meta.
- Messenger + Instagram endpoints:
/webhooks/messengerand/webhooks/instagram - All methods forwarded: forwards GET/POST (and others) to downstream with headers/body intact
- Response mirroring: returns downstream status code, body, and headers (excluding hop-by-hop) to Meta
- Error handling: proper HTTP status codes for missing mapping or downstream failures
- Raw body preservation: forwards exact request bytes to downstream (for signature validation)
-
Install dependencies:
pnpm install
-
Configure environment:
cp .env.example .env
Edit
.envand set:PORT- Server port (default: 3000)MAPPING_PATH- Path to mapping JSON file (default:src/config/mapping.json)FORWARD_TIMEOUT_MS- Request timeout in milliseconds (default: 5000)
-
Configure mapping (local dev): Edit
src/config/mapping.jsonto map page/IG IDs under platform keys:{ "messenger": { "123456789": "https://your-app.com/webhooks/messenger", "123456780": "https://your-app.com/webhooks/messenger" }, "instagram": { "987654321": "https://your-app.com/webhooks/instagram", "987654320": "https://your-app.com/webhooks/instagram" } } -
Start server:
pnpm start
- Ensure Docker + Docker Compose are installed.
- Prepare config volume (mounted read-only): edit
./config/mapping.json(nested platform structure shown above). - Build and run:
docker compose up -d --build
- Exposes host port 80 → container 3000.
- Mounts
./config/mapping.jsoninto the container at/config/mapping.json. - Uses
MAPPING_PATH=/config/mapping.jsonby default.
- Check logs / status:
docker compose logs -f relay docker compose ps
- Update mapping without rebuild: edit
./config/mapping.json, then reload:docker compose kill -s HUP relay - Stop:
docker compose down
- Override env at runtime (example):
FORWARD_TIMEOUT_MS=7000 docker compose up -d
- Go to your Meta App Dashboard → Webhooks product
- Set Messenger Callback URL:
https://your-relay-server.com/webhooks/messenger - Set Instagram Callback URL:
https://your-relay-server.com/webhooks/instagram - Subscribe to desired webhook fields (e.g.,
messages,messaging_postbacksfor Messenger)
curl -X POST http://localhost:3000/webhooks/messenger \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=..." \
-d '{
"object": "page",
"entry": [{
"id": "123456789",
"messaging": [{
"sender": {"id": "user123"},
"recipient": {"id": "123456789"},
"message": {"text": "Hello"}
}]
}]
}'Note: The relay will:
- Extract ID
123456789fromentry[0].id - Look up downstream URL in
mapping.json - Forward the raw request to that URL
- Return the downstream server's response (status, body, headers) back to Meta
curl "http://localhost:3000/webhooks/instagram?test=1"The request (including query params and headers) will be forwarded to the mapped Instagram downstream URL.
curl http://localhost:3000/healthExpected response: {"status":"ok"}
- /webhooks/messenger and /webhooks/instagram (all methods):
- 404: Mapping key not found (tries
entry[0].id→?id/?page_id/?ig_id; optionaldefaultkey under each platform) - 502: Downstream server error, timeout, or network failure
- Mirrors downstream response: If downstream returns 200/204/etc., Meta receives the same status code, body, and headers (except hop-by-hop headers)
- 404: Mapping key not found (tries
Send SIGHUP signal to reload mapping without restarting:
kill -HUP <process_id>For Docker Compose deployments:
docker compose kill -s HUP relay