CapyDB Docs
GuidesIntegrations

Auth0 auth sync

Mirror your Auth0 users into the capydb_auth.users table in your own database, fed by an Auth0 Log Stream or Action webhook.

What it does

Same idea as the Clerk auth sync: CapyDB maintains a capydb_auth.users table inside your project database, kept current from Auth0 user lifecycle events, so user data is a JOIN instead of a Management API call. The table shape is identical across all auth-sync providers.

One difference worth stating up front: the Auth0 sync is webhook-only — there is no backfill from the Auth0 Management API. Existing users land in the table when their next user.created/user.updated event arrives. If you need them sooner, trigger updates (e.g. a bulk metadata touch) or insert them yourself once; the sync upserts over whatever is there.

Setup

  1. Pick a shared secret (any high-entropy string; openssl rand -hex 32 is fine).

  2. Configure the integration with that secret:

    curl -X PUT https://capydb.dev/api/capydb/v1/projects/{projectID}/integrations/auth0 \
      -H "Authorization: Bearer capy_live_..." \
      -H "Content-Type: application/json" \
      -d '{"credentials": {"webhook_signing_secret": "<your shared secret>"}}'

    This creates the capydb_auth schema and users table in your database and stores the secret encrypted. The response's config.webhook_receiver tells you where to point Auth0:

    https://capydb.dev/api/capydb/v1/integrations/auth0/webhook/{projectID}
  3. Point Auth0 at that URL with the same secret as the Authorization value. Two ways to produce the events:

    • Log Stream → Custom Webhook: set the payload to send user events, and set the "Authorization Token" to your shared secret. You will need a small transform (or an Action forwarding) so the body matches the expected shape below.
    • Action (e.g. post-user-registration / post-login): fetch the receiver with an Authorization header carrying the secret.

Verification mechanics

Authentication is the shared secret carried in the Authorization header, compared in constant time against the secret you configured. Auth0 custom webhooks send the configured token verbatim; a Bearer <secret> form is also accepted. Requests with a missing or mismatched header are rejected.

Expected payload

One event object, or a JSON array of them:

{
  "type": "user.created",
  "user": { "user_id": "auth0|abc123", "email": "a@example.com", "...": "..." }
}
  • type (or event — both keys are accepted) is user.created, user.updated, or user.deleted.
  • user is the Auth0 user profile. For deletions, only user.user_id is required.
  • Any other event type is acknowledged with a 2xx and ignored — you can stream broadly without erroring.

Normalization

The Auth0 profile maps onto the shared table like this:

capydb_auth.users columnFrom Auth0
iduser_id (required)
emailemail
first_name / last_namegiven_name / family_name; falls back to splitting name on whitespace (first token / remainder)
usernamenickname
image_urlpicture
created_at / updated_atcreated_at / updated_at (RFC 3339)
last_sign_in_atlast_login
deleted_atset on user.deleted (soft delete; rows are kept)
rawthe full Auth0 user object, verbatim JSONB

Same ground rules as every auth sync: treat the table as read-only, filter with WHERE deleted_at IS NULL, and reach into raw for anything the typed columns do not cover.