# Webhook-driven user provisioning into your own database

## The order

1. Create the webhook endpoint in the Clerk dashboard and copy the signing
   secret shown at creation time. Store it as an env var; never in code.
2. In the handler, verify the signature BEFORE parsing business logic. Clerk
   signs with Svix; pass the raw body and the svix-id, svix-timestamp, and
   svix-signature headers to the verify call. Reject failures with 400/401
   and do nothing else.
3. Keep the route exempt from your normal auth middleware. This endpoint is
   authenticated by the webhook signature, not by a user session.
4. Dedupe by the Svix message id (svix-id header). Clerk retries deliveries,
   so your handler will see the same event twice. Record processed ids and
   return 200 for repeats without side effects.
5. Handle the event types you subscribed to:
   - user.created: create the local row keyed by the Clerk user id.
   - user.updated: update the local row.
   - user.deleted: soft-delete or deactivate, not a hard delete, so audit
     trails and foreign keys survive.
6. Return 200 only after the database write commits. A 500 means Clerk
   retries, which is what you want on real failure and what causes doubles
   if your dedupe is sloppy.

## The ordering trap

Events can arrive out of order. user.updated can beat user.created to your
endpoint. If your updated handler assumes the row exists, it throws and the
retry loop never converges. Two safe patterns: make every handler an upsert
(create if missing), or queue events for unknown users and reconcile on the
next event. Upsert is simpler and usually right.

## Checklist

- Raw body, not parsed JSON, goes into signature verification. Frameworks
  that parse the body first break verification; read the raw bytes.
- Subscribe to the minimum event set you actually handle. Extra events are
  extra failure surface.
- Log the event type and id for every delivery. When a user row looks wrong,
  that log is the first place you look.