Status: ✅ Implemented
Spec Reference: FW-59-external-webhook-triggers.md
Last Updated: 2026-03-22
Overview
FW-59 adds a single inbound webhook surface so external systems can trigger FW workflow executions without bespoke edge functions per integration. The Edge Function performs endpoint resolution, auth, schema validation, and durable enqueue (FW-46). Tenant resolution usespf_organizations.slug plus per-endpoint endpoint_slug.
Not the same as PF inbound webhooks: process-inbound-webhook handles pf_integrations (platform integration registry, stores raw payload on pf_webhook_deliveries). FW-59 targets workflow execution via fw_workflow_definitions and FW-46 — different tables, auth model, and retention/encryption rules.
Integration Points (from Spec)
API / Platform Contracts
Public Edge Function: fw-webhook-receiver
- Supabase URL:
POST https://{project_ref}.supabase.co/functions/v1/fw-webhook-receiver/{org_slug}/{endpoint_slug} - Path parsing: Full pathname includes
/functions/v1/prefix; extractorg_slugandendpoint_slugas the two segments afterfw-webhook-receiver(see process-inbound-webhook for a similarurl.pathname.split('/')approach). - Optional app gateway: Same path may be exposed as
POST https://{app_host}/api/webhooks/{org_slug}/{endpoint_slug}via reverse proxy (implementation choice). - JWT:
verify_jwt: false— callers are not Supabase users; authentication is endpoint-specific (API key, HMAC, or bearer JWT from external IdP). - Runtime credentials: Service role used only inside the function after successful endpoint auth, to call SECURITY DEFINER ingest RPC and write logs (see spec).
- PF-42 call pattern: Call
pf_check_rate_limitwithp_endpointset tofw_webhook:plus the endpoint UUID — align with check-rate-limit (fail-open on DB error is PF-42 default; product may choose fail-closed for abuse-sensitive ingress in a follow-up).
SECURITY DEFINER RPC (planned)
fw_webhook_ingest(...)(name illustrative): Validates resolvedorganization_id, endpoint active state, performs idempotency insert, creates workflow execution row, calls existing FW-46 enqueue path (pgmq.send/ same contract asfw_process_domain_eventworker flow). Invoked by edge with service role; not exposed toanon.fw_webhook_replay_log(...): Authenticated +fw_has_org_access; re-enqueues from stored encrypted payload metadata.
Idempotency
- Header
X-Idempotency-Keyoptional. Dedup key:(endpoint_id, idempotency_key)when header present; else behavior unchanged from spec (payload hash window) for backward compatibility.
Security and Tenant Isolation
- No cross-core imports: all logic stays in FW + platform dependencies.
- Webhook payloads may contain PHI: store ciphertext only; restrict log body viewing to permission
fw.webhooks.view(and replay tofw.webhooks.replay). - Edge must not log raw bodies to console or external APM in production.
- RLS on
fw_webhook_*tables usesfw_has_org_access(organization_id, auth.uid())for authenticated app users; service role bypasses RLS for ingest path only inside controlled RPC.
Database: RLS policy patterns (Supabase / Postgres)
Canonical DDL forfw_webhook_endpoints, fw_webhook_logs, and fw_webhook_secrets (policies, helpers, and indexes) lives in FW-59-external-webhook-triggers.md § Data Model. This section summarizes patterns implementers must follow.
Authenticated app users (authenticated)
- Tenant scope: Every policy on org-scoped tables uses
fw_has_org_access(organization_id, auth.uid())(or, for child rows keyed byendpoint_id, a SECURITY DEFINER helper such asfw_webhook_endpoint_org_id(endpoint_id)so policies do not recurse — constitution §5.7). - WITH CHECK on UPDATE: All
FOR UPDATEpolicies must includeWITH CHECKmirroringUSING(constitution §5.2.9).
Ingest path (service role + RPC only)
- No broad
anonINSERT onfw_webhook_logsor execution tables for ingress. - The Edge Function uses the service role only to call
SECURITY DEFINERRPCs (e.g.fw_webhook_ingest) that validateorganization_id, endpoint state, idempotency, and then enqueue (FW-46). All writes from the public internet are funneled through that contract.
Database: required and recommended indexes
Minimum (tenant + ingress resolution):
Illustrative DDL is in the spec; migrations must stay the source of truth.
Foreign keys:
workflow_definition_id → fw_workflow_definitions(id) must keep ON DELETE RESTRICT (or equivalent) so executions are not orphaned silently.
Webhook execution isolation (multi-tenant / PHI-safe)
- Resolve tenant first: Map path
org_slug→pf_organizations.slug→organization_id. Reject if unknown slug. - Resolve endpoint second: Load
fw_webhook_endpointsby(organization_id, endpoint_slug)(and active / not suspended). Never select an endpoint by slug alone. - Bind workflow to org: The endpoint’s
workflow_definition_idMUST reference a row whoseorganization_idmatches the endpoint’sorganization_id(enforce in RPC before enqueue; add a DB constraint or validate infw_webhook_ingestif the schema allows cross-row checks). - Enqueue: Create / link
fw_workflow_executionsonly for that definition and org; FW-53 and other limits apply after enqueue — no bypass. - Logs / PHI: Encrypted body in
BYTEAonly; structured edge logs contain no raw payload, headers with secrets redacted, and correlation ids only.
Monitoring, alerting, and PF-42 fail-open behavior
PF-42’scheck-rate-limit Edge Function fails open on DB or unexpected errors (returns allowed: true) and logs:
DB error in rate limit checkwith{ error, orgId, endpoint }Unexpected error in rate limit checkwith{ error }
fw-webhook-receiver MUST emit structured logs on the same failure modes when calling pf_check_rate_limit (or inline RPC), with fields aligned for dashboards:
Alert thresholds (starting defaults — tune per environment)
- ≥ 10
pf42_*/pf42_rate_limit_*log lines per minute per project (or perorg_slugif high-volume single-tenant). - OR ≥ 5% of
fw-webhook-receiverrequests hitting the fail-open path over a 5-minute rolling window.
Channels and escalation
- Slack:
#platform-alertsor#fw-webhooksfor warning-level sustained rates. - Pager / on-call (SEV-2+): Opsgenie/PagerDuty rotation shared by Platform SRE and FW module owner when fail-open volume indicates abuse or prolonged PF-42/DB outage.
Incident response playbook (short)
- Triage: Confirm spike in Edge logs / Supabase metrics; segment by
org_slugandendpoint_id. - Infra: Check Postgres availability,
pf_check_rate_limiterrors, andpf_rate_limitsrow presence for affected endpoints (FR-2.1a). - Mitigate: Enable
FW_WEBHOOK_PF42_FAIL_CLOSED(see below) if fail-open is amplifying abuse or hiding enforcement; communicate to affected orgs if ingress is degraded. - Postmortem: For SEV-2+, document timeline, blast radius, and corrective actions within 72 hours.
Feature flag: fail-closed toggle
Event Contracts
FW-59 does not publish a new cross-core domain event in Phase 1. Optional future:fw_webhook_received for observability (defer).
Related Docs
- FW-59 Spec
- FW-59 CONTEXT — ingress edge decisions (PF-42 fail-open; points here for monitoring)
- FW-46 Durable Execution Worker
- FW-16 Event-Based Workflow Triggers
- PF-42 Rate Limiting
- PF-45 Feature Flags —
FW_WEBHOOK_PF42_FAIL_CLOSEDconsumer pattern - check-rate-limit Edge Function — reference log messages for fail-open
- CROSS_CORE_INTEGRATIONS.md