Context RepoContext Repo Docs

Webhooks

ContextRepo webhooks deliver real-time notifications for user, billing, and prompt/document/collection events. Includes payload schema, signature verification with Svix, and retry behavior.

ContextRepo emits webhook events so external systems can react in real time to changes in your workspace. Events are delivered as POST requests with a JSON body and Svix-signed headers — verify the signature on every incoming request before trusting the payload.

Webhooks today are delivered via Clerk's hosted Svix infrastructure. Configure subscriptions and rotate signing secrets from the Clerk dashboard for the ContextRepo application.

Endpoint

ContextRepo's first-party webhook receiver is hosted at:

POST https://contextrepo.com/clerk-users-webhook

This endpoint accepts Clerk-emitted events (user lifecycle and Clerk Billing payment attempts) and updates the underlying Convex tables. If you operate a custom integration that needs to fan out events to your own systems, register your URL with Svix and forward from your own infrastructure — ContextRepo does not relay outbound webhooks at this time.

Event Categories

CategoryEventsSource
User lifecycleuser.created, user.updated, user.deletedClerk
BillingpaymentAttempt.updatedClerk Billing
Prompt changes(planned)ContextRepo
Document changes(planned)ContextRepo
Collection changes(planned)ContextRepo

The user-lifecycle and billing events are live today. The prompt, document, and collection-level events are reserved for a future ContextRepo-native webhook surface; they are not yet emitted.

Payload Format

Every webhook body is JSON with the following envelope:

Example: user.created
{
  "data": {
    "id": "user_2abc123def456",
    "email_addresses": [
      { "email_address": "user@example.com", "verification": { "status": "verified" } }
    ],
    "created_at": 1710432000000,
    "updated_at": 1710432000000
  },
  "object": "event",
  "type": "user.created"
}

The type field distinguishes events; the data field carries the resource payload (shape varies per event type — see the Clerk webhook reference for the authoritative schema).

Signature Verification

Every webhook request carries Svix headers that authenticate the payload:

HeaderDescription
svix-idUnique message ID — use for idempotency
svix-timestampUnix timestamp the event was emitted
svix-signatureHMAC-SHA256 signature of the raw body

Always verify the signature before processing the body. A signed-payload check rejects forgeries, replay attacks, and misrouted events from other tenants.

Verify with @clerk/clerk-sdk-node
import { Webhook } from "svix"

const SIGNING_SECRET = process.env.CLERK_WEBHOOK_SECRET!

export async function POST(req: Request) {
  const payload = await req.text()
  const headers = {
    "svix-id": req.headers.get("svix-id")!,
    "svix-timestamp": req.headers.get("svix-timestamp")!,
    "svix-signature": req.headers.get("svix-signature")!,
  }

  let event
  try {
    event = new Webhook(SIGNING_SECRET).verify(payload, headers)
  } catch (err) {
    return new Response("Invalid signature", { status: 400 })
  }

  // event is verified — safe to process
  return new Response("ok", { status: 200 })
}

Retry Behavior

Svix retries failed deliveries with exponential backoff:

  • Initial attempt: immediately after the source event
  • Retry schedule: 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 1 day
  • Failure criteria: any non-2xx response, or no response within 15 seconds
  • Max retries: 7 attempts before the message moves to the dead-letter queue

Return a 2xx status code as soon as you have safely persisted the event (or determined it is a duplicate). If your handler is slow, queue the work and respond 200 immediately — long-running handlers will time out and trigger spurious retries.

Idempotency

Use the svix-id header as your idempotency key. Store it in a deduplication table or short-lived cache and short-circuit any subsequent delivery with the same ID. Svix guarantees at-least-once delivery, not exactly-once, so idempotency is the receiver's responsibility.

Local Development

To test webhook delivery against a local server:

Tunnel a public URL
ngrok http 3000

Then point the Clerk webhook URL at the resulting https://<id>.ngrok.io/clerk-users-webhook. The Clerk dashboard's "Replay" button is the fastest way to iterate on a single event without performing live actions.

Security Checklist

  • Verify the Svix signature on every request — no exceptions
  • Use HTTPS endpoints only — Svix refuses to deliver to plain HTTP
  • Rotate the signing secret after any suspected compromise (Clerk dashboard)
  • Treat webhook bodies as untrusted user input until verified
  • Log the svix-id for every accepted event so duplicate-suppression is auditable
  • Do not place secrets in URL query parameters — use signing secrets via process.env