Skip to content

Repository files navigation

door-webhook

Public edge receiver for CiviCRM webhooks, part of the door-sync system. It sits between CiviCRM (which fires a signed webhook via a CiviRules custom action) and the door-sync daemon on a Raspberry Pi behind NAT.

CiviCRM (CiviRules action) ──HMAC POST──▶ door-webhook (this Worker)
                                              │  validate + buffer on a Queue
                                              ▼
                                          Queue consumer ──HMAC POST + CF Access──▶
                                              door-sync (Pi, via Cloudflare Tunnel)
  • fetch verifies the request signature (CIVICRM_WEBHOOK_SECRET), normalizes it to a WebhookEvent, sends it to the EVENTS queue, and returns 202 immediately. A bad/missing/stale signature returns 401.
  • queue consumer pushes each event to the Pi at ${ORIGIN_URL}/civicrm/membership-changed, re-signing with ORIGIN_HMAC_SECRET and presenting a Cloudflare Access service token. A non-2xx response retries; after max_retries the message is dead-lettered (door-webhook-dlq) rather than dropped — so a Pi reboot never loses an event.

Signing contract

Both hops use the same scheme (shared with the Pi's Python receiver):

X-Door-Sync-Timestamp: <unix seconds>
X-Door-Sync-Signature: sha256=<hex>
hex = HMAC_SHA256(secret, `${timestamp}.` + rawBody)

The verifier rejects a timestamp more than 300s from now (replay guard).

Setup

wrangler.jsonc holds no account id, no hostname and no secrets, so it is safe to publish. The account comes from a git-ignored .env, secrets from wrangler secret put, and ORIGIN_URL from the Cloudflare dashboard — keep_vars: true is what stops each deploy from deleting it.

npm install

# 1. Point the CLI at the right Cloudflare account. Without this wrangler
#    refuses to guess when your login can reach more than one account.
cp .env.example .env      # then set CLOUDFLARE_ACCOUNT_ID (npx wrangler whoami)

# 2. Create the queues (once per account):
npx wrangler queues create door-webhook-events
npx wrangler queues create door-webhook-dlq

# 3. Set ORIGIN_URL in the Cloudflare dashboard (Worker -> Settings -> Variables)
#    to the Pi's Cloudflare Tunnel hostname — scheme + host only, the Worker
#    appends the path. It is a plain var, not a secret, so its value stays
#    readable there; `keep_vars` in wrangler.jsonc keeps deploys from wiping it.

# 4. Set the secrets:
npx wrangler secret put CIVICRM_WEBHOOK_SECRET    # shared with the CiviRules action
npx wrangler secret put ORIGIN_HMAC_SECRET        # shared with the Pi (WEBHOOK_HMAC_SECRET)
npx wrangler secret put CF_ACCESS_CLIENT_ID       # Access service token for the tunnel
npx wrangler secret put CF_ACCESS_CLIENT_SECRET

# 5. Deploy:
npm run deploy

The queue consumer checks all of these before it signs a delivery, so a missing one is logged by name (missing Worker config: ORIGIN_URL, ...) rather than surfacing as a crypto error.

For local development, copy .dev.vars.example to .dev.vars (git-ignored) and fill it in, ORIGIN_URL included — wrangler types reads that file to type the bindings, so re-run npm run cf-typegen after adding a key there or changing wrangler.jsonc. Use .dev.vars rather than .env for these: if both exist, .dev.vars wins and Worker bindings in .env are silently ignored. .env is only for the wrangler CLI's own variables, such as CLOUDFLARE_ACCOUNT_ID.

Commands

Command Purpose
npm run dev Local dev server at http://localhost:8787
npm test -- run Run the Vitest suite once
npm run deploy Deploy to Cloudflare
npm run cf-typegen Regenerate worker-configuration.d.ts from wrangler.jsonc

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages