Webhooks
Outbound webhooks sign events with HMAC-SHA256 and ship them to your URL; inbound webhooks accept signed payloads and emit platform events that trigger rules and workflows can react to.
Webhooks are how ProxifAI connects to systems that aren’t covered by a first-party integration. Outbound webhooks ship platform events to any HTTPS endpoint with HMAC-signed bodies. Inbound webhooks accept signed payloads from external systems and turn them into webhook.* platform events that trigger rules and workflow triggers: blocks can react to.
Outbound webhooks
When a platform event fires (issue created, PR merged, agent finished, …), every subscribed outbound webhook gets a POST request with the event as the JSON body. Implementation: internal/integrations/events.go.
Request shape
POST https://your-app.example.com/proxifai-webhook HTTP/1.1
Content-Type: application/json
X-ProxifAI-Event: issue.created
X-ProxifAI-Delivery: del_01H2T4...
X-ProxifAI-Signature: sha256=<hex>
{
"id": "evt_…",
"type": "issue.created",
"source": "proxifai",
"subject": "issue/abc123",
"orgId": "org_…",
"timestamp": "2026-05-05T13:14:15Z",
"data": {
"issueId": "abc123",
"title": "Fix login bug",
"status": "todo",
"priority": "high",
"teamId": "team1",
"assigneeId": "user1"
}
}
The body is the PlatformEvent JSON (internal/integrations/types.go) — same shape that trigger-rule conditions evaluate against. The data payload follows the JSON-Schema declared by the event type (see the built-in event catalog).
Verifying signatures
X-ProxifAI-Signature is sha256=<hex> where <hex> is HMAC-SHA256(body, secret) (events.go:287).
# Bash
EXPECTED="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')"
[ "$EXPECTED" = "$RECEIVED_SIGNATURE" ] && echo OK
# Python
import hmac, hashlib
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
hmac.compare_digest(expected, received_signature)
// Go
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
hmac.Equal([]byte(expected), []byte(receivedSignature))
Always use a constant-time compare. Reject requests where the header is missing or the comparison fails.
Creating an outbound webhook
pfai outbound-webhook create \
--url https://your-app.example.com/proxifai-webhook \
--events issue.created,issue.status_changed,pr.merged \
--secret "$(openssl rand -hex 32)"
Or via the API:
curl -X POST http://localhost:3000/api/v1/outbound-webhooks \
-H "Authorization: Bearer $PFAI_TOKEN" \
-d '{
"url": "https://your-app.example.com/proxifai-webhook",
"events": ["issue.created","pr.merged"],
"secret": "..."
}'
The secret is stored encrypted at rest. Subscribe to ["*"] to receive every event type.
Delivery, retries, and tracking
Each delivery writes a row to WebhookDelivery with status (pending / success / failed). The retry policy is intentionally minimal in OSS — failures increment the webhook’s failureCount field but the delivery worker doesn’t auto-retry beyond the initial attempt. To rebuild a more aggressive retry behavior, run a workflow that listens for webhook.failed (a synthetic event the delivery worker emits on 5xx) and re-fires.
Tracked per webhook:
| Field | Source |
|---|---|
sendCount, failureCount, lastSentAt | Updated on every delivery attempt |
Per-delivery row in WebhookDelivery | delivery_id (X-ProxifAI-Delivery), event type, status, latency, response code |
Inspect via pfai outbound-webhook ls/view <id> or GET /api/v1/outbound-webhooks/{id}/deliveries.
Event coverage
Every entry in the built-in event catalog is deliverable. Use ["*"] to subscribe to all 17, or pick specific types. Custom event types created via pfai event-type create are also deliverable.
Inbound webhooks
External systems POST to ProxifAI; ProxifAI authenticates the request, normalizes it into a PlatformEvent, and publishes it on the event bus where trigger rules and workflows can react.
URL format
POST /api/v1/webhooks/inbound/<slug>
The slug is generated when you create an integration — it’s the secret. Anyone with the URL can deliver events to your org, so treat it like a credential.
curl -X POST http://localhost:3000/api/v1/webhooks/inbound/wh_a1b2c3... \
-H "Content-Type: application/json" \
-H "X-Event-Type: alert.fired" \
-d '{"severity":"critical","message":"DB latency > 500ms"}'
Authentication
Two layered defenses:
- The slug is unguessable — generated as a random token, scoped to a single integration row.
- HMAC signature (optional but recommended) — provide a secret on the integration; ProxifAI verifies the body against
X-Hub-Signature-256(GitHub-style) orX-Webhook-Signature(internal/integrations/generic/generic.go:36). Format issha256=<hex>.
Without a configured secret, the slug is the only authentication. Always set a secret for production endpoints.
Provider routing
Inbound webhooks are dispatched to the provider implementation matching the integration’s provider field:
provider | Use case | Behavior |
|---|---|---|
generic_webhook | Catch-all for systems without a dedicated provider | Wraps the body into a webhook.received event with the X-Event-Type header (or X-GitHub-Event if present) as eventType in the data |
slack | Slack Events API + slash commands | Verifies Slack’s signing secret, parses event, dispatches to commands/messages |
mcp_server | Stub | The MCP integration provider exists for symmetry but rejects inbound webhooks (the MCP server lives at /api/v1/mcp/*, not here) |
Custom providers can be registered via integrations.RegisterProvider to handle vendor-specific signing schemes.
Reacting to inbound events
Once the event is published, two systems can act on it:
- Trigger rules —
event_source+event_typefilter +ConditionGroup→ action (dispatch_agent,create_issue,send_webhook,dispatch_workflow) - Workflow YAML triggers —
triggers: [{type: event, event: webhook.received}]blocks fire workflows directly
Both have full access to the event’s data payload via the standard JMESPath-style field references.
REST endpoints
| Method · Path | Purpose |
|---|---|
GET /api/v1/outbound-webhooks | List outbound webhooks |
POST /api/v1/outbound-webhooks | Create — {url, events[], secret} |
PATCH /api/v1/outbound-webhooks/{id} | Update target URL, event subscriptions, or secret |
DELETE /api/v1/outbound-webhooks/{id} | Delete |
GET /api/v1/outbound-webhooks/{id}/deliveries | Per-delivery audit log |
POST /api/v1/integrations | Create an integration (which provisions the inbound URL) |
POST /api/v1/webhooks/inbound/{slug} | Deliver an inbound event |
pfai outbound-webhook and pfai integration wrap these.
See also
Once an inbound webhook is in, trigger rules and workflow YAML decide what runs.
Different shape: external clients drive ProxifAI tools instead of pushing events.
A first-class integration that uses the inbound webhook plumbing for Slack events.
The 17 ProxifAI events available for outbound subscription.