Messaging Webhook Handler Template

Purpose

Produces a production-ready webhook handler skeleton for a new messaging vendor. Every generated handler enforces the five governance requirements from webhook-security-hardening-2026-04-20.md: IP filter, signature verification, dedup via processed_webhook_events, non-blocking audit write to webhook_audit_log with local-file fallback, and dead-letter persistence.

Stops each team from reimplementing webhook hardening per vendor.

When to use

  • During Phase 0B vendor onboarding after vendor-sms-onboarding skill completes
  • When adding a second webhook path for an existing vendor (e.g. separate DLR endpoint)
  • When a vendor changes signature verification scheme and the handler needs regeneration

Do NOT use for outbound-only vendors (no inbound webhook) or non-messaging webhooks (HubSpot, DocuSign have their own handlers).

Inputs

FlagTypeRequiredDescription
--vendorstringyesMatches messaging_providers.name
--auth-typeenumyesOne of: hmac-sha256, hmac-ed25519, basic-auth, bearer
--portintyesDistinct local port per vendor (see port map below)
--pathstringyesWebhook URL path, e.g. /webhook/telnyx-sms
--dedup-key-fnstringnoJS expression to extract dedup key, e.g. event.data.id

Outputs

  • workspace/webhooks/<vendor>-handler.js — handler wired to shared tools: webhook-sig-verify.js, phone-normalize.js, processed-webhook-events.js, webhook-audit-log.js
  • ~/.config/systemd/user/<vendor>-webhook.service — unit file loading master.env, auto-restart
  • Updated workspace/FUNNEL-REGISTRY.md with new row (path, provider, port, sig-verify method, dedup, audit log, last reviewed date)
  • Updated workspace/knowledge-base/security/WEBHOOK-IP-RANGES.md under the vendor heading
  • Cloudflare Tunnel ingress rule hint emitted to stdout (requires separate Cloudflare plan + approval before applying per feedback_cloudflare_plan_before_execute.md)

Acceptance tests

  1. Handler file exists, passes node --check, chmod 755
  2. GET /<path>/health returns {"status":"ok","vendor":"<name>"}
  3. POST with invalid signature returns 401
  4. POST with valid sig + duplicate event returns 200 {"duplicate":true}
  5. POST with valid sig + new event returns 200 + row in webhook_audit_log
  6. With DB disconnected, handler still returns 200 and persists raw event to /var/log/openclaw/webhook-fallback-<vendor>.jsonl
  7. Systemd unit passes daemon-reload + boots after enable --now
  8. FUNNEL-REGISTRY contains the new row; next security-audit-funnel.timer run reports clean

Rollback behavior

  1. systemctl --user disable --now <vendor>-webhook.service
  2. Remove systemd unit file
  3. Remove Cloudflare Tunnel route via Cloudflare plan (never modify Cloudflare without a plan per governance)
  4. Revert FUNNEL-REGISTRY.md and WEBHOOK-IP-RANGES.md edits
  5. Delete handler file
  6. Set messaging_providers.status = 'pending' for the rolled-back vendor

Port map

Per plan Section H.0B.1-6:

  • telnyx: 18794
  • bandwidth: 18795
  • sent.dm: 18796
  • twilio-voice: 18797 (existing)
  • loopmessage: 18798
  • bird: 18799
  • linq: 18800
  • Plan sections: G.5 and H.0B.1-6
  • Shared deps: tools/webhook-sig-verify.js, tools/phone-normalize.js, tools/processed-webhook-events.js, tools/webhook-audit-log.js
  • Governance: workspace/FUNNEL-REGISTRY.md, workspace/knowledge-base/security/WEBHOOK-IP-RANGES.md
  • Prior-art handlers: workspace/webhooks/salesmessage-handler-v4-complete.js, workspace/webhooks/quo-handler-enhanced.js
  • Webhook security plan: /home/opsadmin/.claude/plans/webhook-security-hardening-2026-04-20.md