bside-webhook-proxy
Multi-project webhook proxy for incoming webhooks (Square, Stripe, GitHub, etc.).
Services like Square can deliver webhooks but cannot add custom HTTP headers like X-Appwrite-Project. This proxy receives those webhooks, looks up the matching project's API key from its environment config, and forwards the request to the right Appwrite function with the proper auth headers.
One proxy serves all projects — routing is driven entirely by environment variables.
Written in TypeScript (src/server.ts), compiled to plain JS at build time.
How it works
Caller (Square, etc.)
│
│ POST raw webhook payload
▼
https://app-proxy.bsidesolutions.net/square
│
│ Express server resolves:
│ ROUTE_MAP["/square"] = { apiKeySlot: "square", functionId: "verify_square_webhook" }
│ PROJECT_CREDENTIALS["square"] = { project: "staging-crown-x-ms-monet", apiKey: "..." }
│
▼
POST https://appwrite.bsidesolutions.net/v1/functions/verify_square_webhook/executions
Headers: X-Appwrite-Project + X-Appwrite-Key
│
▼
Appwrite function runs, returns response, proxy returns it to caller.
Quick start
Local development
cp .env.example .env
# edit .env to fill in PROJECT_CREDENTIALS and ROUTE_MAP
npm install
# Run with hot-reload TypeScript directly
npm run dev
# Or build + run compiled output
npm run build
npm start
Docker
docker build -t bside-webhook-proxy .
docker run --rm -p 3012:3012 \
-e PROJECT_CREDENTIALS='{"square":{"project":"...","apiKey":"..."}}' \
-e ROUTE_MAP='/square=>{"apiKeySlot":"square","functionId":"verify_square_webhook"}' \
bside-webhook-proxy
Coolify
- In Coolify → Add New Resource → Application → Public/Private Repo
- Point at
bside-solutions/bside-webhook-proxy - Coolify auto-builds the Dockerfile (multi-stage build compiles TypeScript)
- Set the domain (e.g.
app-proxy.bsidesolutions.net) - Add environment variables (see Configuration)
- Deploy
Configuration
All credentials live in environment variables — nothing secret is ever committed.
PROJECT_CREDENTIALS (required)
JSON object mapping slot names → { project, apiKey }.
{
"square": {
"project": "staging-crown-x-ms-monet",
"apiKey": "<appwrite-api-key-with-functions.write-scope>"
},
"stripe": {
"project": "staging-other-project",
"apiKey": "<appwrite-api-key-with-functions.write-scope>"
}
}
ROUTE_MAP (optional)
Maps short paths to { apiKeySlot, functionId }. Format:
/path=>{"apiKeySlot":"slot-name","functionId":"function-name"};
/path2=>{"apiKeySlot":"slot-name-2","functionId":"function-name-2"}
Example:
/square=>{"apiKeySlot":"square","functionId":"verify_square_webhook"};
/stripe=>{"apiKeySlot":"stripe","functionId":"verify_stripe_webhook"}
If a short-path route matches, the proxy uses its config directly. Otherwise, the caller must pass project, function, and apiKey as querystring params.
APPWRITE_BASE_URL (optional)
Default: https://appwrite.bsidesolutions.net/v1. Override if your Appwrite API lives elsewhere.
PORT (optional)
Default: 3012.
Webhook URL formats
Either of these work:
Short path (uses ROUTE_MAP)
POST https://app-proxy.bsidesolutions.net/square
Explicit querystring (always works, no route config needed)
POST https://app-proxy.bsidesolutions.net?project=staging-crown-x-ms-monet&function=verify_square_webhook&apiKey=square
Health check
GET https://app-proxy.bsidesolutions.net/health
Returns:
{
"status": "ok",
"projects": ["square"],
"routes": ["/square"],
"timestamp": "2026-07-07T..."
}
Adding a new project / webhook
- Generate an API key in the target Appwrite project with
functions.writescope - Update
PROJECT_CREDENTIALSJSON env var on the proxy service to include the new slot - Add a route entry to
ROUTE_MAPenv var if you want a short URL - Restart the proxy container
No code changes needed.
Project structure
bside-webhook-proxy/
├── src/
│ └── server.ts # TypeScript source (Express server)
├── dist/ # Compiled JS output (gitignored)
├── server.js # Entry point - requires compiled ./dist/server.js
├── package.json # Build + start scripts, deps
├── tsconfig.json # TypeScript compiler config
├── Dockerfile # Multi-stage build (TypeScript -> JS)
├── docker-compose.yml # Example Coolify deploy snippet
├── .env.example # Sample env vars
└── README.md
License
MIT