Skip to content

Latest commit

 

History

History
83 lines (63 loc) · 2.6 KB

File metadata and controls

83 lines (63 loc) · 2.6 KB

Webhooks quickstart (Step 3.9)

Five minutes to register an outbound webhook, receive a signed event, and verify its signature.

0. See it without a server

ragctl webhooks demo                 # sign + deliver a sample event in-process
ragctl webhooks demo --fail 2        # watch the retry path (2 failures, then success)

It prints the delivery status, the X-AgentContextOS-Signature header, and whether the signature verifies — no gateway, no network.

1. Start a gateway

uv run uvicorn rag_gateway.app:app --port 8000

2. Register a subscription (secret returned once)

curl -sX POST localhost:8000/v1/webhooks/subscriptions \
  -H 'x-tenant-id: acme' -H 'x-principal-id: svc' \
  -d '{"url":"https://your-app.example/webhooks","event_types":["ingest.completed"]}'

The response includes "secret": "whsec_…"copy it now; every later read masks it. event_types: [] subscribes to all events.

3. Fire a test delivery

curl -sX POST localhost:8000/v1/webhooks/subscriptions/whsub_…/test \
  -H 'x-tenant-id: acme' -H 'x-principal-id: svc'

Your endpoint receives a POST with the signed envelope. A real ingest.completed fires automatically whenever a document finishes ingesting via POST /v1/ingest/document.

4. Verify the signature on your side

from rag_webhooks import verify   # or re-implement the scheme in your language

@app.post("/webhooks")
async def receive(request):
    body = await request.body()
    sig = request.headers["X-AgentContextOS-Signature"]
    if not verify(body, MY_SECRET, sig, tolerance_s=300):
        return Response(status_code=400)
    event = json.loads(body)
    # dedupe on event["id"] — delivery is at-least-once
    ...
    return Response(status_code=200)   # 2xx ACKs the delivery

The HMAC is HMAC-SHA256(secret, f"{timestamp}.{body}"); the header is t=<timestamp>,v1=<hex>. Reject deliveries whose timestamp is outside your tolerance (replay protection), and dedupe on event.id — a retry after a slow ACK can deliver the same id twice.

5. Wire it in production via rag.yaml

webhooks:
  max_attempts: 5
  timeout_s: 8.0
  subscriptions:
    - tenant_id: acme
      url: https://your-app.example/webhooks
      event_types: [ingest.completed, audit.policy_violation]
      secret: ${ACME_WEBHOOK_SECRET}

What's next