Configuration

Deleting and Exporting Users

Delete a user's EmbedAuth sign-in from your own server with your app's client secret, and, in GDPR mode, fetch a copy of everything EmbedAuth holds about them.

3 min readUpdated October 14, 2026

When a user deletes their account on your site, their sign-in at EmbedAuth (email address, password, linked providers and sessions) should go too. There are two ways to do it:

  • The account widget's Delete tab. The user confirms with their password inside the widget, and you learn about it from the authframe:account-deleted event and the user.deleted webhook.
  • Your own server, with your app's client secret. Use this when the "Delete account" button is part of your own page. This page covers that route.

Your client secret

Your app's client secret starts with afs_. It is shown once, when the app is created, and again whenever you choose Rotate secret on the app's page in the dashboard. Rotating it makes the old one stop working at once. Keep it on your server: it can delete any user of the app, so it must never reach a browser.

Deleting a user

curl -X DELETE \
  -H "Authorization: Bearer $EMBEDAUTH_CLIENT_SECRET" \
  https://embedauth.com/api/v1/apps/YOUR_CLIENT_ID/users/USER_ID

USER_ID is the sub claim of the user's tokens. You can pass the user's email address instead.

StatusBodyMeaning
200{"deleted": true}The user is gone
200{"deleted": false}There was no such user, perhaps because an earlier call already deleted them. Retrying is safe
401{"error": "..."}The secret is wrong, or the client id names no app

What is deleted

The user, their password, linked sign-in providers, sessions, password-reset and magic links, codes, and rate-limit counters all go. Sign-in events stay in your statistics, with no user, IP address or browser attached. Audit log entries keep what happened, not who did it. Webhook deliveries that named the user are removed from the delivery log.

The deletion is recorded in your audit log as end_user.deleted and sent to your webhooks as user.deleted, the same as a deletion from the account widget.

A delete button on your site

Delete your own records and the sign-in together, so neither outlives the other. One way is to do both inside your database transaction:

await db.transaction(async (tx) => {
  await tx.query('DELETE FROM users WHERE id = $1', [user.id]);
  const res = await fetch(
    `https://embedauth.com/api/v1/apps/${CLIENT_ID}/users/${user.embedauthId}`,
    { method: 'DELETE', headers: { authorization: `Bearer ${process.env.EMBEDAUTH_CLIENT_SECRET}` } },
  );
  if (!res.ok) throw new Error(`EmbedAuth refused the deletion (${res.status})`);
  // Committing now: both are gone. If EmbedAuth refused, nothing was.
});

Exporting a user's data

For an app in GDPR mode (Pro and Enterprise plans, switched on from the app's GDPR page), your server can fetch a copy of everything EmbedAuth holds about a user, to answer an access or portability request. Apps that are not in GDPR mode get 403.

curl https://embedauth.com/api/v1/apps/$CLIENT_ID/users/$USER_ID/export \
  -H "Authorization: Bearer $EMBEDAUTH_CLIENT_SECRET"

As with deletion, $USER_ID is the sub of the user's tokens or their email address. The answer is JSON:

  • account: the email address, name, avatar, whether the address is verified, whether a password is set, and when the account was created and last changed.
  • linkedAccounts: each linked Google or Discord account, with the profile the provider returned.
  • sessions: each signed-in device, with when it signed in, when it was last active, its IP address and browser, and whether it has ended.
  • signInEvents: sign-ins, sign-ups and failed attempts, with IP address and browser.
  • accountActivity: changes the user made in the account widget.

Secrets are never included: no password hash, no tokens. An unknown user answers 404. In GDPR mode, users can also download the same copy themselves from the account widget's Download your data link, and the app's GDPR page in the dashboard downloads every user at once.