> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.id.me/shared-signals-framework/user-verification-events/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.id.me/_mcp/server. # User verification events > Subscribe your application to receive real-time user verification events from IDMe. IDMe requires two bits of information to create a User Verification Event Stream: * **Receiver URL:** The public URL to which IDMe should stream events.\ Example: `https://example.com/api/v1/events` * **Authentication Method:** If protected by authentication, the details of the auth pattern. We support: #### No auth #### Basic auth #### Bearer tokens #### OAuth2 If your Receiver URL uses another form of authentication, please reach out to us at [partnersupport@id.me](mailto:partnersupport@id.me). # Architecture ```mermaid flowchart TD subgraph Setup["Audience setup"] A["Set up Receiver URL"] B["Subscribe to one or more applications"] C["Select event handles to receive"] A --> B --> C end subgraph Process["User verification event flow"] U["User begins verification for an Audience application"] E["Verification events are emitted"] S{"Is the Audience subscribed to this application and event handle?"} N["Event is not delivered to the Audience"] P["Event proceeds through the pipeline"] K{"Has the user consented to share information with this application?"} D["Event is delivered to the Audience Receiver URL"] M["Event is embargoed"] G["User later grants consent (or accepts the legal agreement, if enabled for the Audience)"] R["Embargoed events are released"] RD["Released events are delivered to the Receiver URL"] U --> E E --> S S -- "No" --> N S -- "Yes" --> P P --> K K -- "Yes" --> D K -- "No" --> M M --> G G --> R R --> RD end Setup --> Process ``` # Setup your audience Set up your Receiver URL Specify one or more applications you want your audience to subscribe to Specify the event handles you want to receive # Process A user begins the verification process for an Audience's application Events are emitted If the Audience is subscribed to the event handle, the event proceeds through the pipeline A check is performed to see if the user has consented to share information with your application If consent has been granted by the user, the event is emitted to your Audience's Receiver URL If consent has not been granted by the user, the event is embargoed After the user consents to sharing information with your application, embargoed events are released and delivered to your Receiver URL. See [Embargo and release](#embargo-and-release). # Embargo and release ID.me tracks a consent status for each user and Audience pair. Until the user has consented to share information with one of your Audience's applications, their events are held (embargoed) instead of delivered. Embargoed events are kept, not dropped. When they are released, every held event is delivered to your Receiver URL in one batch. | Consent status | What happens to new events | | ----------------- | ---------------------------------------------------------------------------------------------------------- | | Embargoed | The event is held until the embargo is released. This is the default for a user who has not yet consented. | | Allowed | The event is delivered as soon as it is processed. | | Denied or revoked | The event is held. It is not delivered unless the user later consents again. | If a user has already consented to one of your Audience's applications, they start in the **Allowed** status and their events are never embargoed. ## What releases an embargo | Trigger | Availability | Behavior | | ------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `consent.allow` | All Audiences (default) | The user grants consent to your application. The status becomes **Allowed**, and all held events are released. This includes users who had previously denied or revoked consent. | | `account.agreement` | By request, enabled per Audience | The user accepts your legal agreement (Terms of Service and Privacy Policy). If the user is still **Embargoed**, the status becomes **Allowed** and all held events are released. | > **Note** > > `account.agreement` only releases an embargo that is still in place. It never overrides a user's explicit choice: if the user has denied or revoked consent, their events stay held until they send a new `consent.allow`. If the user is already **Allowed**, `account.agreement` is delivered like any other event and nothing is released again. Released events carry their original payload and `created_at`, so receivers can order them by when they happened rather than by when they arrived. ## Request embargo release on legal agreement Some integrations collect the user's agreement to share data through their own legal agreement instead of a separate consent screen. If your integration works this way, you can ask ID.me to treat `account.agreement` as consent for your Audience. Contact your ID.me account team or [partnersupport@id.me](mailto:partnersupport@id.me). Say that you want embargoed user verification events released on legal agreement. List each Audience to enable, in each environment (ID.me Labs sandbox and production). Embargo release on agreement is enabled one Audience at a time, so any Audience you leave out keeps the default `consent.allow` behavior. Tell us how your legal agreement covers sharing identity verification event data with your organization. ID.me reviews this before enabling the change. After approval, ID.me subscribes each Audience to the `account.agreement` event handle and enables release on agreement. This is done in the Labs sandbox first. Test the change in the Labs sandbox. Start verification as a test user who has not consented, accept the legal agreement, and confirm that the held events and the `account.agreement` event reach your Receiver URL. Then let us know so we can enable it in production. > **Warning** > > Release on agreement affects only events that are still embargoed when the user accepts the agreement, plus every event after that. Events for users who denied or revoked consent are never released by `account.agreement`. # Event schema Each event is delivered as a JSON payload in the body of an HTTP `POST` to your Receiver URL. The top-level fields carry routing metadata about where the event came from, and the nested `event` object carries the event itself. **`Payload shape`** ```json Payload shape { "host": "...", "source": "...", "sourcetype": "...", "event": { "event": "...", ... } } ``` Every event carries the same set of common fields. Events that relate to a specific IDMe resource also carry that resource, in full, on the `object` field. | Field | Type | Description | | ------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------- | | `host` | string | The IDMe events host that delivered the event. `events.idmelabs.com` in the IDMe Labs sandbox, `events.id.me` in production. | | `source` | string | The IDMe environment that emitted the event. `idme:dev` in the IDMe Labs sandbox, `idme:prod` in production. | | `sourcetype` | string | The category of event being delivered. Always `idme:auth` for user verification events. | | `event` | object | The event itself. See [The event object](#the-event-object). | ## The event object | Field | Type | Description | Example value | | -------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `event_id` | string | Event identifier | `2737913b-4f42-4140-a474-39720eaf9cd5` | | `event` | string | Event type. See [Event types](#event-types). | `session.signin` | | `uuid` | string | IDMe unique user identifier | `c3850702f02f4f948cede41610c2a715` | | `user_email` | string | User's email address | `xbzi.gqcv@gmail.com` | | `application_name` | string | Name of the application associated with the event | `Auth Portal` | | `ipaddress` | string | IP address associated with the event | `55.55.55.55` | | `device_fingerprint` | string | Device fingerprint associated with the event | `lu2pFqAQyLIr0MqVpDEV` | | `useragent` | string | Operating system, vendor, and/or version of the requesting user agent | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0` | | `created_at` | string | Date and time the event was created, in ISO 8601 format | `2026-07-04T13:12:26.877091Z` | | `eid` | nullable string | Customer-supplied context echoed back on the payload | | | `app_id` | nullable string | Customer-supplied context echoed back on the payload | | | `state` | nullable object or string | The value the customer passed in the OAuth `state` parameter, echoed back exactly as supplied. A JSON object is delivered as a structured object, and a plain string is delivered as a string. | | | `object` | object | The IDMe resource this event relates to. Present only on events that carry a resource. See [The object field](#the-object-field). | | > **Note** > > `eid`, `app_id`, and `state` carry customer-supplied context. They are additive and nullable: each is always present on the payload, with a `null` value when the event carries no such context. ## The object field The `object` field carries the full resource an event relates to, so you do not need a follow-up API call to act on the event. Its shape varies by event type, and `event` is the discriminator: read `event` first, then parse `object` according to that event's resource. | Event | Resource | | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | [`session.verification_status`](/shared-signals-framework/user-verification-events/pre-verified-events) | A verification status: what IDMe already knows about the user when they arrive, before multifactor authentication | | All other events | None. The `object` field is omitted. | > **Note** > > `object` is optional. Events with no associated resource omit the field entirely rather than delivering `"object": null`, so check for its presence before reading it. > **Note** > > `object` does not change which events you receive. Subscription, consent, and embargo behave exactly as described in [Process](#process): an event carrying a resource is gated on user consent on the same terms as any other event, and is released with its resource intact once consent is granted. > > Audience field mappings apply only to `eid`, `app_id`, and `state`. Fields inside `object` cannot be renamed. ### Events with no resource Events that describe an action rather than a resource carry only the common fields, with no `object` field. **`session.signin`** ```json session.signin { "host": "events.idmelabs.com", "source": "idme:dev", "sourcetype": "idme:auth", "event": { "event_id": "4c7e2a19-6b58-42d3-8e91-05f7c3a1b6d2", "event": "session.signin", "uuid": "c3850702f02f4f948cede41610c2a715", "user_email": "xbzi.gqcv@gmail.com", "application_name": "Auth Portal", "ipaddress": "55.55.55.55", "device_fingerprint": "lu2pFqAQyLIr0MqVpDEV", "useragent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "created_at": "2026-07-04T13:12:26.877091Z", "eid": null, "app_id": null, "state": null } } ``` ## Handling events Because `object` is present only on some events, branch on `event` before reading it. ```javascript const { event, object } = payload.event; switch (event) { case "session.verification_status": // object is a verification status if (object.is_preverified_for_policy) skipOnboarding(); break; default: // object is undefined: the event carries no resource break; } ``` > **Warning** > > New fields may be added to any resource, and new resource types may be introduced, without a breaking change. Ignore fields you do not recognize rather than rejecting the payload. # Event types | Event | Description | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `2fa.complete` | Successful completion of multifactor authentication. | | `2fa.delivery` | The user's chosen multifactor authentication option is sent to the user's device. | | `2fa.download` | User downloaded the recovery code. | | `2fa.initiate` | MFA method is selected. | | `2fa.options` | MFA methods are presented to the user. | | `2fa.recovery.complete` | The recovery code is successfully used for MFA. | | `2fa.recovery.confirm` | User clicks link in email to confirm updating MFA settings. | | `2fa.recovery.delivery` | Email sent to the user after requesting to change their MFA settings when logging in. | | `2fa.recovery.failure` | User's information entered to update their MFA cannot be validated and fails. | | `2fa.response` | The user enters the MFA code during authentication. | | `2fa.revoke` | User removes an MFA method from My Account. | | `account.agreement` | User accepts Terms of Service and Privacy Policy | | `account.legal_agreement.granted` | User accepts a legal agreement presented for an integration | | `account.legal_agreement.shown` | User is shown a legal agreement for an integration | | `account.lockout.delivery` | The user is sent an email to help with account recovery after lockout due to too many bad password attempts | | `account.password.change` | Email notification to the user once they've reset their password in the "forgot password" flow | | `account.password.complete` | Password was successfully reset | | `account.password.delivery` | User submitted their username in the "forgot password" flow and receives an email to start reset password flow | | `account.password.failure` | Reset password failed because the link clicked from the email to reset password is no longer valid | | `account.password.response` | User clicked link in the email taking them to the reset password page | | `appointment.start` | User starts the supervised flow | | `consent.allow` | User allowed consent to authorize the \[integration] to access identity verification data. | | `consent.deny` | User denied consent to authorize the \[integration] to access identity verification data. | | `consent.revoke` | User revoked consent for an integration to access their data from their My Account settings. | | `email.account.confirm` | The newly added email address to My Account was confirmed (via link click in email) | | `email.account.delivery` | The "confirm email" is sent to newly added email to confirm ownership before officially adding the email to My Account | | `email.confirmation.complete` | Email was successfully confirmed by clicking the prompt in the email or entering the 6 digit code into the screen | | `email.confirmation.delivery` | An email was sent to the registered email to confirm ownership of the email | | `email.create` | An email is added in the user's IDMe My Account | | `email.primary` | Email in My Account was made the primary email | | `email.remove` | Email listed in My Account was removed | | `federation.confirmation.complete` | User taps the CTA in their email to confirm connecting their account | | `federation.confirmation.delivery` | User signs in using a social login (LinkedIn, etc.) and taps "Connect" to connect their IDMe account using that email | | `federation.connect` | Social login is added as an option from My Account | | `federation.remove` | Social login is removed as an option from My Account | | `inperson.appointments.attempt_created` | An identity attempt is created for the user. | | `inperson.appointments.booked` | The appointment is scheduled for the selected timeslot at the selected location. | | `inperson.appointments.closed` | Other in-progress appointments for a user are closed when an appointment is marked as credentialed. | | `inperson.appointments.credentialed` | The user identity verification is completed and accepted. | | `inperson.appointments.denied` | An IDMe document reviewer has rejected one or more identity documents. | | `inperson.appointments.documents_uploaded` | The user's identity documents have been uploaded. | | `inperson.appointments.expired` | The appointment expires if the user does not complete in-person verification within 30 days. This is followed by a request to Sterling to cancel the order. | | `inperson.appointments.failed_dupe` | The user failed the dupe check. | | `inperson.appointments.in_store_verified` | The user has submitted their information at a kiosk and it has been reviewed by the in person technician. The payload has been received by IDMe from Sterling. | | `inperson.appointments.info_reviewed` | The user has confirmed their submitted PII and appointment details. | | `inperson.appointments.location_selected` | The users searches for a kiosk location by city or zip code and makes a selection. | | `inperson.appointments.order_canceled` | The appointment will be cancelled if it has been replaced by a new appointment or expired. | | `inperson.appointments.order_created` | The appointment request is sent to Sterling, which returns a registration code that the user can enter or scan via a QR code at the kiosk. (Note: this is distinct from the previous request to book the timeslot at the selected location). | | `inperson.appointments.pending_review` | The user is asked to review their PII and appointment details. | | `inperson.appointments.pii_collected` | The user has submitted their PII on the PII entry screen. | | `inperson.appointments.pii_mismatch` | The user failed pii mismatch. | | `inperson.appointments.replaced` | The appointment was replaced by another. This is followed by a request to Sterling to cancel the order. | | `inperson.appointments.review_requested` | An IDMe review of identity documents has been requested for the user. | | `inperson.user.already_verified` | A user attempted to create an appointment but was informed that they are already verified. | | `session.failed` | Incorrect password is provided when signing in | | `session.limited` | User is already signed in within a different browser | | `session.lockout.applied` | Account is locked after too many failed password attempts | | `session.lockout.expired` | 72 hours has passed after the locked has been applied. This re-enables the ability for the user to attempt login | | `session.signin` | User successfully signed in to their IDMe account | | `session.signout` | User logs out of their IDMe account | | `session.signup` | Successfully created a new IDMe account by inputting an email and password | | `session.suspended` | User attempts to take an action but cannot proceed due to account suspension | | `session.timeout` | A user's session times out due to inactivity | | `session.verification_status` | Emitted after login or signup and before multifactor authentication: whether the user already holds the identity level the requesting policy requires. `event.object` is a [verification status](/shared-signals-framework/user-verification-events/pre-verified-events). | | `status.revocation.applied` | User successfully closed their IDMe account | | `status.revocation.requested` | User in My Account takes action to close their account | | `status.signal.applied` | Indicates an account takeover (ATO) has been detected and the user is attempting to reclaim the account. The status allows the account to complete re-verification. | | `status.suspension.applied` | A user's account is suspended preventing the user from using their IDMe login | | `status.suspension.removed` | The user's account suspensions status is removed | | `supervised.appointments.attended` | User made an appointment, waiting for the appointment time, acknowledged they were present, then joined the video session. | | `supervised.appointments.canceled` | User made an appointment, then either rescheduled or canceled the appointment before they were able to attend the appointment. | | `supervised.appointments.created` | User created an appointment. | | `supervised.appointments.missed` | User created an appointment, left, but did not rejoin the waiting room around the appointment time. | | `supervised.appointments.schedule_link_clicked` | User clicked on the create appointment link on the waiting page. | | `transaction.complete` | SAML or OAuth success or failure | | `verification.choice.dtr_full` | User chose "Video Chat Agent" on alternate options screen with DTR Lite not enabled. | | `verification.choice.dtr_lite` | User chose "Video Chat Agent" on alternate options screen with DTR Lite enabled. | | `verification.choice.in_person` | User chose In-Person Verification from alternate options screen. | | `verification.choice.unsupervised` | User chose "Self Service" on alternate options screen. | | `verification.complete` | Successfully completed verification. | | `verification.initiate` | Unsupervised identity verification method is selected (i.e. document is selected). | | `verification.liveness.checkup` | Successfully completed liveness after initial NIST IAL2 + Liveness verification. | | `verification.options` | Verification methods are presented to the user when DTR is enabled (i.e. Self-service or Verify on a video call). | | `verification.phone.authorization.approved` | Approved activity and email address associated with the account when transitioning to mobile. | | `verification.phone.authorization.denied` | Denied activity and email address associated with the account when transitioning to mobile. | | `verification.progress` | User is shown the "Review Info" screen at the end of unsupervised verification. | | `verification.redirect.alternate_supervised` | User voluntarily selects to verify via the supervised flow rather than being routed to the supervised flow from unsupervised (i.e., Direct-to-TR) when TR Lite is not enabled for DTR. | | `verification.transition.documents.app_links` | Mobile app links step presented. | | `verification.transition.documents.awaiting_mobile` | Waiting on the user to finish capture on mobile. | | `verification.transition.documents.document_review` | User is shown their captured document for review. | | `verification.transition.documents.mobile_handoff_error` | Mobile handoff failed. | | `verification.transition.documents.photo_upload` | Photo document upload step presented. | | `verification.transition.documents.qr_capture` | QR code capture step presented for mobile handoff. | | `verification.transition.documents.smart_upload` | Guided (smart) document capture step presented. | | `verification.transition.documents.text_phone` | Step to text a capture link to the user's phone. | | `verification.transition.documents.upload` | Document upload step entered. | | `verification.transition.documents.upload_file` | File-based document upload step presented. | > Subscribe your application to receive real-time user verification events from IDMe. ## Docs - [Multifactor events](https://docs.id.me/shared-signals-framework/user-verification-events/multifactor-events.md): The payload delivered on 2fa.* user verification events. - [Pre-verified events](https://docs.id.me/shared-signals-framework/user-verification-events/pre-verified-events.md): The verification_status resource delivered on the session.verification_status user verification event.