Skip to main content
Causeloop has two directions of webhook traffic:
  • Inbound — a third-party system posts events to Causeloop. Causeloop verifies the HMAC signature and ingests the payload.
  • Outbound — Causeloop posts events to your endpoint when something happens (sync completed, issue ingested, pattern detected). Your endpoint verifies the signature.

Inbound webhooks

Inbound webhooks let any system push events directly into Causeloop without polling. The endpoint authenticates callers via HMAC-SHA256 — there is no Bearer token. The endpoint fails closed: a missing or invalid signature always returns 401.

Endpoint

POST /v1/webhooks/inbound/{connector_id}
This endpoint is public (no Bearer auth). The connector_id identifies which connector’s signing secret to use for verification.

Required headers

HeaderDescription
x-causeloop-signatureHMAC-SHA256 hex digest of the raw request body. Preferred.
x-hub-signature-256Alternative: sha256=<hex> format (GitHub-compatible).
Provide one of the two headers. If you use x-hub-signature-256, prefix the digest with sha256=.

How signature verification works

1

Get the signing secret

When you configure an inbound webhook for a connector, Causeloop generates a signing_secret (e.g. whsec_a1b2c3...). Store it securely in your source system.
2

Sign the raw body

Before sending, compute HMAC-SHA256(signing_secret, raw_body) and include the hex digest in the x-causeloop-signature header.
3

Causeloop verifies

Causeloop buffers the raw request bytes (before any JSON parsing), recomputes the HMAC using the stored secret, and compares with a constant-time comparison. A mismatch, missing signature, or missing secret all return 401.
The HMAC is computed over the raw bytes of the request body. Do not parse, pretty-print, or re-serialize the JSON before signing — the byte sequence must be identical to what Causeloop receives.

Signing an outgoing payload (sender side)

import hashlib
import hmac
import json
import httpx

SIGNING_SECRET = "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
CONNECTOR_ID   = "con_01hx4k2m9p"

payload = {"event_type": "alert.fired", "alert_id": "ALT-9999", "severity": "critical"}
body = json.dumps(payload, separators=(",", ":")).encode()

signature = hmac.new(
    SIGNING_SECRET.encode("utf-8"),
    body,
    hashlib.sha256,
).hexdigest()

resp = httpx.post(
    f"https://api.causeloop.ai/v1/webhooks/inbound/{CONNECTOR_ID}",
    content=body,
    headers={
        "Content-Type": "application/json",
        "x-causeloop-signature": signature,
    },
)
print(resp.status_code)  # 202

Inbound webhook response

202 Accepted — payload received, signature valid, delivery recorded.
{
  "received": true,
  "delivery_id": "del_01hx5d4e5f",
  "connector_id": "con_01hx4k2m9p"
}
401 Unauthorized — missing or invalid signature, or no signing secret configured for this connector.

Registering an inbound webhook

To enable inbound delivery for a connector, create an outbound webhook record that references the connector. Causeloop auto-generates the signing secret:
curl -X POST https://api.causeloop.ai/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.causeloop.ai/v1/webhooks/inbound/con_01hx4k2m9p",
    "connector_id": "con_01hx4k2m9p",
    "description": "PagerDuty inbound events"
  }'
The response includes signing_secret — copy it now. Causeloop does not show it again (use the rotate endpoint to retrieve a new one).

Outbound webhooks

Outbound webhooks let Causeloop notify your systems when events occur. Causeloop signs every delivery with x-causeloop-signature. You verify the signature on your end before processing.

Create an outbound webhook

curl -X POST https://api.causeloop.ai/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/hooks/causeloop",
    "event_types": ["connector.sync.failed", "connector.sync.succeeded", "issue.ingested"],
    "description": "Alert on sync failures"
  }'
Response (201 Created):
{
  "id": "wh_01hx5c3d4e",
  "url": "https://your-app.example.com/hooks/causeloop",
  "event_types": ["connector.sync.failed", "connector.sync.succeeded", "issue.ingested"],
  "enabled": true,
  "signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "created_at": "2026-06-14T10:00:00Z"
}
The signing_secret is only returned at creation and after rotation. Store it securely immediately.

Webhook event types

EventFired when
connector.sync.succeededAn incremental or full sync completed successfully
connector.sync.failedA sync run failed
connector.inbound.receivedAn inbound webhook payload was verified and accepted
issue.ingestedA single issue was ingested (API push or inbound webhook)
issue.batch_ingestedA batch ingest job completed
webhook.createdA new webhook was registered
webhook.secret.rotatedA webhook signing secret was rotated
Leave event_types empty or omit it to receive all events.

Verifying outbound webhook signatures

Every outbound delivery includes x-causeloop-signature: <hex> computed as HMAC-SHA256(signing_secret, raw_body).
import hashlib
import hmac
import json
from fastapi import Request, HTTPException

async def verify_causeloop_webhook(request: Request, secret: str) -> dict:
    raw_body = await request.body()
    provided_sig = request.headers.get("x-causeloop-signature", "")

    expected_sig = hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected_sig, provided_sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    return json.loads(raw_body)
Always use a constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node). Standard string equality is vulnerable to timing attacks.

Rotating the signing secret

curl -X POST https://api.causeloop.ai/v1/webhooks/wh_01hx5c3d4e/rotate \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "signing_secret": "whsec_newSecretHere...",
  "rotated_at": "2026-06-14T11:00:00Z"
}
Update your endpoint’s secret immediately — the old secret stops working once the new one is issued.

Viewing delivery history

curl https://api.causeloop.ai/v1/webhooks/wh_01hx5c3d4e/deliveries \
  -H "Authorization: Bearer $TOKEN"
Each delivery record includes:
FieldDescription
idUnique delivery ID
event_typeThe event that triggered the delivery
status_codeHTTP response code from your endpoint
delivered_atISO 8601 delivery timestamp
duration_msRound-trip time in milliseconds
payload_previewFirst 200 bytes of the delivered payload

Managing webhooks

# List all webhooks
GET /v1/webhooks

# Update a webhook
PATCH /v1/webhooks/{id}   # fields: url, event_types, enabled, description

# Delete a webhook
DELETE /v1/webhooks/{id}
Required scopes: webhooks:read for GET endpoints, webhooks:admin for POST/PATCH/DELETE.