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-webhookThis 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
| Category | Events | Source |
|---|---|---|
| User lifecycle | user.created, user.updated, user.deleted | Clerk |
| Billing | paymentAttempt.updated | Clerk 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:
{
"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:
| Header | Description |
|---|---|
svix-id | Unique message ID — use for idempotency |
svix-timestamp | Unix timestamp the event was emitted |
svix-signature | HMAC-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.
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:
ngrok http 3000Then 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-idfor every accepted event so duplicate-suppression is auditable - Do not place secrets in URL query parameters — use signing secrets via
process.env
Related References
- REST API Reference: The HTTP API that webhooks accompany
- Authentication: Bearer JWT and API-Key setup
- Endpoints: The full REST endpoint catalog