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.


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", key: "..." }
   │
   ▼
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

cp .env.example .env
# edit .env to fill in PROJECT_CREDENTIALS and ROUTE_MAP

npm install
npm start

Docker

docker build -t bside-webhook-proxy .
docker run --rm -p 3012:3012 \
  -e PROJECT_CREDENTIALS='{"square":{"project":"...","key":"..."}}' \
  -e ROUTE_MAP='/square=>{"apiKeySlot":"square","functionId":"verify_square_webhook"}' \
  bside-webhook-proxy

Coolify

  1. In Coolify → Add New ResourceApplicationPublic/Private Repo
  2. Point at bside-solutions/bside-webhook-proxy
  3. Coolify auto-builds the Dockerfile
  4. Set the domain (e.g. app-proxy.bsidesolutions.net)
  5. Add environment variables (see Configuration)
  6. 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

  1. Generate an API key in the target Appwrite project with functions.write scope
  2. Update PROJECT_CREDENTIALS JSON env var on the proxy service to include the new slot
  3. Add a route entry to ROUTE_MAP env var if you want a short URL
  4. Restart the proxy container

No code changes needed.


Local development

npm install
npm start

To test forwarding:

curl -X POST 'http://localhost:3012/square' \
  -H 'Content-Type: application/json' \
  -d '{"test": true}'

License

MIT

Description
Multi-project webhook proxy for incoming webhooks
Readme 81 KiB
Languages
TypeScript 89.6%
Dockerfile 7.5%
JavaScript 2.9%