HonkIO Auth

Auth webhook events

Nineteen events for users, sessions, organizations and security, delivered by your existing HonkIO webhooks.

Subscribing

HonkIO Auth events use your account’s existing webhooks: the same signing with X-HonkIO-Signature and X-HonkIO-Timestamp (Signing), the same retries and dead-letter handling (Retries). Subscribe to them like any other event:

cURL
curl -X POST https://api.honkio.ca/v1/webhooks \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.acme.ca/hooks/honkio",
    "events": ["user.created", "user.deleted", "session.revoked", "membership.deleted"]
  }'

Each event’s data carries app_id, and livemode follows the app’s mode. Payloads never contain codes, tokens, keys or secrets. The API key that creates a webhook for these events, or changes its URL, also needs read access to their data: auth_users for user, session, sign-in and sign-up events, auth_organizations for organization and membership events, and auth_apps for app events. A key without it gets 403 FORBIDDEN, and sees those events’ dead letters without their payload.

User events

user.created

A user signed up, or accepted an invitation as a new user.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user": {
    "id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "email": "ada@example.com",
    "email_verified": true,
    "phone": null,
    "phone_verified": false,
    "name": "Ada Lovelace",
    "blocked": false,
    "created_at": "2026-11-02T15:04:05.000Z",
    "last_sign_in_at": "2026-11-02T15:04:05.000Z"
  }
}

user.updated

A user’s profile or sign-in methods changed; changes names the fields.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user": {
    "id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "email": "ada@example.com",
    "email_verified": true,
    "phone": "+1416XXXXXXX",
    "phone_verified": true,
    "name": "Ada Lovelace",
    "blocked": false,
    "created_at": "2026-11-02T15:04:05.000Z",
    "last_sign_in_at": "2026-11-02T15:04:05.000Z"
  },
  "changes": [
    "phone"
  ]
}

user.deleted

A user was deleted. Only the ID remains.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user": {
    "id": "usr_clxxxuserxxxxxxxxxxxxxx"
  }
}

user.blocked

A user was blocked, with your reason.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user": {
    "id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "email": "ada@example.com",
    "email_verified": true,
    "phone": null,
    "phone_verified": false,
    "name": "Ada Lovelace",
    "blocked": true,
    "created_at": "2026-11-02T15:04:05.000Z",
    "last_sign_in_at": "2026-11-02T15:04:05.000Z"
  },
  "reason": "Chargeback fraud"
}

user.locked

An existing user’s identifier was locked after too many wrong codes, until locked_until. Links and passkeys still work.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
  "locked_until": "2026-11-02T16:04:05.000Z"
}

Session events

session.created

A user signed in.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "session": {
    "id": "ses_xxxxxxxxxxxxxxxxxxxxxx",
    "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "amr": [
      "otp"
    ],
    "organization_id": null,
    "created_at": "2026-11-02T15:04:05.000Z",
    "expires_at": "2026-12-02T15:04:05.000Z"
  }
}

session.revoked

A session ended before it expired.

reason is one of:

ReasonMeaning
logoutthe user signed out
refresh_reusean old refresh token was reused, so the whole session was revoked
revokedyour back end revoked the refresh token at /revoke
refresh_refuseda refresh was refused, for example after removal from the organization
not_methe user clicked “This wasn’t me” on a new-device alert
apiyou revoked it through the API or the dashboard
user_blockedthe user was blocked
method_changeda sign-in method was added, changed or removed
staffHonkIO staff ended it
hostname_changedthe app moved to a new hostname, so its issuer changed
Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "session": {
    "id": "ses_xxxxxxxxxxxxxxxxxxxxxx",
    "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "amr": [
      "otp"
    ],
    "organization_id": null,
    "created_at": "2026-11-02T15:04:05.000Z",
    "expires_at": "2026-12-02T15:04:05.000Z"
  },
  "reason": "logout"
}

Organization events

organization.created

An organization was created.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "organization": {
    "id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "name": "Northwind",
    "slug": "northwind",
    "created_at": "2026-11-02T15:04:05.000Z"
  }
}

organization.updated

An organization was renamed or its settings changed.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "organization": {
    "id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "name": "Northwind Traders",
    "slug": "northwind",
    "created_at": "2026-11-02T15:04:05.000Z"
  }
}

organization.deleted

An organization was deleted.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "organization": {
    "id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "name": "Northwind",
    "slug": "northwind",
    "created_at": "2026-11-02T15:04:05.000Z"
  }
}

membership.created

A user joined an organization.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "membership": {
    "organization_id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "role": "member"
  }
}

membership.updated

A member’s role changed.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "membership": {
    "organization_id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "role": "admin"
  }
}

membership.deleted

A user left or was removed from an organization. Sent at once; their next refresh in it fails.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "membership": {
    "organization_id": "org_clxxxorgxxxxxxxxxxxxxxx",
    "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
    "role": "member"
  }
}

Security events

signin.suspicious

A risky sign-in was challenged or flagged.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "user_id": "usr_clxxxuserxxxxxxxxxxxxxx",
  "risk_level": "high",
  "reasons": [
    "datacenter_network",
    "new_device"
  ],
  "new_device": true,
  "flagged": true
}

signup.blocked

Sign-ups were blocked in the last minute: a count by reason, sent at most once a minute.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "window_start": "2026-11-02T15:04:00.000Z",
  "window_end": "2026-11-02T15:05:00.000Z",
  "count": 42,
  "reasons": {
    "throwaway_email": 30,
    "risk": 12
  }
}

app.attack_mode.started

The app went into attack mode, automatically or by hand.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "trigger": "auto",
  "started_at": "2026-11-02T15:04:05.000Z"
}

app.attack_mode.ended

Attack mode ended.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "trigger": "auto",
  "started_at": "2026-11-02T15:04:05.000Z",
  "ended_at": "2026-11-02T17:30:00.000Z"
}

app.updated

The app moved to a new hostname (PATCH with hostname): changes is ["hostname"], with the new hostname and issuer, the previous_hostname and previous_issuer, how many sessions ended and how many passkeys were retired.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "changes": [
    "hostname"
  ],
  "hostname": "auth.acme.ca",
  "issuer": "https://auth.acme.ca",
  "previous_hostname": "login.acme.ca",
  "previous_issuer": "https://login.acme.ca",
  "sessions_revoked": 42,
  "passkeys_retired": 0
}

sms.spend_limit_reached

The daily text spending limit was reached and text sign-in paused.

Example data
{
  "app_id": "clxxxappxxxxxxxxxxxxxxxxx",
  "limit_cents": 500,
  "spent_cents": 503,
  "day": "2026-11-02"
}