Skip to content

Event reference ​

All event types with their SDK constant on Anikeen\Id\Webhooks\EventType. Opt-in events are only sent to endpoints that select them explicitly (* doesn't include them).

TypeConstantOpt-in
order.createdORDER_CREATED
order.completedORDER_COMPLETED
order.payment_failedORDER_PAYMENT_FAILED
order.cancelledORDER_CANCELLED
order.shippedORDER_SHIPPED
order.refundedORDER_REFUNDED
subscription.createdSUBSCRIPTION_CREATED
subscription.activatedSUBSCRIPTION_ACTIVATED
subscription.pausedSUBSCRIPTION_PAUSED
subscription.resumedSUBSCRIPTION_RESUMED
subscription.cancelledSUBSCRIPTION_CANCELLED
subscription.expiredSUBSCRIPTION_EXPIRED
subscription.trial_will_endSUBSCRIPTION_TRIAL_WILL_END
subscription.discount_endedSUBSCRIPTION_DISCOUNT_ENDED
subscription.updatedSUBSCRIPTION_UPDATED
subscription.renewedSUBSCRIPTION_RENEWEDyes
checkout.completedCHECKOUT_COMPLETED
checkout.cancelledCHECKOUT_CANCELLED
checkout.expiredCHECKOUT_EXPIRED
billing_limit.reachedBILLING_LIMIT_REACHEDyes
billing_limit.availableBILLING_LIMIT_AVAILABLEyes
user.updatedUSER_UPDATEDyes
user.deletedUSER_DELETEDyes
preference.updatedPREFERENCE_UPDATEDyes
app_access.revoked betaAPP_ACCESS_REVOKEDyes
webhook.testTEST

Order events ​

data.object is the order. It carries the group and metadata your app set, both addresses, oauth_client_id (the app that bills the order), the order's invoice (null before payment; on order.completed usually still status: pending while the PDF is rendered – pdf_url stays valid) and items with type, image_url, subscription_id, period_start, period_end, net_amount, vat_amount, gross_amount, description, meter, charge_id, monthly_cap and discount.

TypeWhen
order.createdan order of your app was created (not for monthly bills)
order.completedthe order was paid: fulfil it now
order.payment_faileda charge was declined; it is retried automatically
order.cancelledthe user cancelled, a draft expired, or the order was revoked
order.shippeda shipment was recorded; the order carries its shipments
order.refundedmoney was given back (a refund or a partial cancellation, with a credit note)

order.refunded: the order has refunded_total and refunds: [{id, kind, status, method, reason, net_total, vat_total, gross_total, credit_note_number, created_at, items: [{order_item_id, name, units, net_amount, gross_amount, vat_rate}]}] (kindgoodwill or cancellation). The order's totals stay what was invoiced. A full refund also cancels the order, so order.cancelled is sent too; don't rely on the order of the two events.

Subscription events ​

data.object is the subscription.

TypeWhenExtra data
subscription.createda subscription was created
subscription.activatedit started (checkout, confirmation or a trial start)
subscription.pausedit was paused
subscription.resumedit was resumed
subscription.cancelledit was cancelledreason: trial_ended_without_payment_method when the billing ended it, else null
subscription.expireda paused subscription ran out
subscription.trial_will_enda free trial ends in a few dayspayment_method_missing
subscription.discount_endedthe last discounted period was billed
subscription.updateda change of a running subscription took effectprevious_attributes: the old values of the changed fields
subscription.reneweda period was booked (opt-in, see below)period

subscription.renewed ​

Sent once for every period booked on the user's monthly bill, the first one included, so you can follow every charge of a subscription from this event alone. Hourly subscriptions renew every hour, so this event is opt-in.

json
{
  "type": "subscription.renewed",
  "data": {
    "object": {"id": "01K…", "status": "active", "…": "…"},
    "period": {
      "object": "subscription_period",
      "subscription_id": "01K…",
      "period_start": "2026-11-01T00:00:00Z",
      "period_end": "2026-12-01T00:00:00Z",
      "periods": 1,
      "first_period": false,
      "unit": "month",
      "order_id": "01K…",
      "order_item_id": "01K…",
      "items": [{"order_item_id": "01K…", "units": 1, "unit_price": "6.714286", "net_amount": "6.71", "monthly_cap": null, "discount": null}],
      "currency_iso": "EUR",
      "net_amount": "6.71"
    }
  }
}
  • period_end is exclusive. periods > 1 when several periods were booked at once (e.g. elapsed periods at checkout).
  • net_amount is what this booking adds (net, after a discount).
  • items are the lines it was booked on (with a monthly cap also the rest of the cap and the free units at price 0; with a discount also the negative discount line).
  • first_period is true for the subscription's first paid period – the checkout (sent after subscription.activated), also with elapsed periods, or the first period after a free trial – and false for every later one (renewals, resuming an expired subscription, a unit change applied right away).
php
if ($event['type'] === EventType::SUBSCRIPTION_RENEWED) {
    $period = $event['data']['period'];
    Commission::record($period['subscription_id'], $period['net_amount'], first: $period['first_period']);
}

Checkout events ​

data.object is the checkout session. Sent after the events of its subscriptions.

TypeWhen
checkout.completedthe user confirmed: all subscriptions are running
checkout.cancelledthe user or your app cancelled the session; its drafts were cancelled
checkout.expiredit wasn't confirmed within 24 hours; its drafts were cancelled

Billing limit events ​

billing_limit.reached and billing_limit.available (opt-in): data.object is the billing limit as your app reads it, data.reason says why. Sent only to apps that may read the limit of a user connected to them. Ignore events whose version is lower than or equal to the one you hold.

User events ​

Opt-in, sent only to apps the user is connected to.

user.updated ​

Sent for changes to name, username, the verified email address, email_verified and the profile picture, from any source (the user's settings, the profile API, support). Untrusted apps get it only while the user has given them a token with user, user:read or *; the app that made the change gets it too.

json
{
  "id": "01JEVT…",
  "type": "user.updated",
  "created": 1762500000,
  "data": {
    "object": {
      "object": "user",
      "id": "01JUSER…",
      "name": "Erika Mustermann",
      "username": "erika",
      "email": "erika@example.com",
      "email_verified": true,
      "avatar": "https://id.anikeen.com/avatars/01JUSER…",
      "version": 8
    },
    "changed": ["name", "avatar"]
  }
}
  • data.object is the full profile; changed names the changed fields (never old values).
  • A new email address arrives only once it is verified: email is null while the address isn't, and a requested change sends nothing until the user confirms it (then changed is ["email", "email_verified"] or ["email"]).
  • avatar is a stable URL; avatar in changed means the picture behind it changed: drop a cached copy.
  • Versions: version grows with every such change (profile_version in the REST shapes). Keep the highest version you have seen and ignore older ones. Catch up on a missed event with GET /api/v1/client/users/{user}.
  • Trusted apps also get role (and role in changed); a change of the role alone is sent to them only.

user.deleted ​

json
{"data": {"object": {"object": "user", "id": "01JUSER…", "deleted": true, "deleted_at": "2026-11-07T10:00:00+00:00"}}}

Final and sent once: delete or anonymise your copy of the account (and what you keep only for it), and stop acting for the user. No other event about the user follows it. If a user.updated arrives later for the same ID, support restored the account: treat it as a returning user.

preference.updated ​

Opt-in, sent to trusted apps that may read the namespace – never to the app that made the change.

json
{
  "data": {
    "object": {
      "object": "preference",
      "user_id": "01JUSER…",
      "namespace": "acme",
      "key": "onboarding",
      "version": 1043,
      "deleted": false,
      "updated_at": "2026-11-06T10:00:00Z",
      "updated_by": {"type": "user", "client_id": "9f0c…"}
    }
  }
}

Thin, never the value: fetch the preference again, or drop your copy when deleted is true. Ignore events with a lower version than the one you hold (versions are per user). Nothing is sent for a write that changes nothing, nor when the account is deleted (user.deleted covers that).

app_access.revoked beta ​

A user removed an app's access in Anikeen ID (Settings → Connected apps), or Anikeen revoked the app or no longer trusts it: the app's tokens for that user stop working.

json
{
  "data": {
    "object": {
      "object": "app_access",
      "user_id": "01JUSER…",
      "client": {"id": "9f0c…", "name": "Example App"},
      "revoked_at": "2026-11-09T10:00:00+00:00"
    }
  }
}
  • Opt-in, sent only to trusted apps the user is connected to, one event per app – never to the app that lost access (it learns it from 401 answers and a failing refresh).
  • Meant for apps that hold data another app managed for the user, e.g. the Anikeen Console releases what the revoked app managed for the user.
  • Not sent when the user had no working token of the app any more.

webhook.test ​

A test event to check your endpoint. Answer it with a 2xx like any other event.

Anikeen ID developer documentation