Events
Events notify you of state changes in enrollments, certificates, deliveries, and other entities. This page covers event structure, delivery, and what each event means.
Event principles
Section titled “Event principles”Events are emitted through a transactional outbox. An event exists if and only if the state change it describes was committed. There is no path that changes state without emitting, or emits without changing state.
Events are thin. They carry identifiers, the state transition, and correlation data — not personal data, not identity evidence, not document content, not credential material. When you receive an event, you fetch the full details by calling the API under your own authorization.
Delivery is at least once. Consumers must deduplicate by event identifier. An event may be retried and redelivered; the id field is the deduplication key.
Ordering is not guaranteed. Each event carries an aggregate_version (the version of the entity that changed) so you can detect and order events yourself if order matters.
Schemas evolve additively. New fields may appear; existing fields do not change meaning. Tolerate unknown fields and unknown event types.
Event envelope
Section titled “Event envelope”Every event has this structure:
{ "id": "evt_8Fq2LmVw9dKpRt4X", "type": "certificate.issued", "api_version": "1.0.0", "occurred_at": "2026-09-23T14:30:00Z", "aggregate": { "type": "certificate", "id": "cert_abc123", "version": 5 }, "environment": "production", "request_id": "req_8Fq2LmVw9dKpRt4X", "correlation_id": "your_correlation_value", "data": { "subscriber_id": "sub_xyz789", "certificate_status": "issued_on_hold" }}| Field | Meaning |
|---|---|
id |
Unique identifier for this event. Use this to deduplicate, including across redeliveries. |
type |
Dotted event type (e.g., certificate.issued). Additive: tolerate unknown types. |
api_version |
Schema version of the event payload. Additive changes do not increment. |
occurred_at |
RFC 3339 UTC timestamp of when the state change committed. |
aggregate |
The entity that changed: type, id, and version. Order by version to handle redelivery. |
environment |
sandbox or production. Never ambiguous. |
request_id |
The API request that caused this event, where one exists. |
correlation_id |
Your supplied correlation ID, where one was sent in the request. |
data |
Identifiers and transition detail. Thin; never personal data or credential material. |
A redelivery carries the original id and occurred_at. It is not a new event. If you receive an event and later see the same id again, it is a retry, not a state change.
Delivery
Section titled “Delivery”Webhook registration
Section titled “Webhook registration”You register webhook endpoints in the developer console:
- Provide an HTTPS URL where the platform will send events
- Verify control. The platform sends a
GETrequest with a verification token. Respond with200and the token echoed back to prove you control the endpoint. - Review delivery history. Inspect what was sent, what responses you returned, and retry attempts.
- Rotate endpoints. Register a new endpoint and retire the old one with a grace period for event redelivery.
Signing
Section titled “Signing”Events are signed asymmetrically. The platform signs with a private key and publishes the corresponding verification key. You hold no secret.
Every webhook delivery carries:
| Header | Meaning |
|---|---|
X-Trust-Signature |
Asymmetric signature over the raw request body |
X-Trust-Signed-At |
RFC 3339 UTC timestamp of when the signature was made |
X-Trust-Event-Id |
The event id for replay protection |
Verification steps:
- Fetch the platform’s current verification keys from the published location.
- Verify the signature over the raw body before parsing JSON.
- Check that
X-Trust-Signed-Atis within the allowed replay window (e.g., last 5 minutes). - Deduplicate by
X-Trust-Event-Id, including across redeliveries. - Only then parse and process the JSON.
If verification fails at any step, reject the request.
Two properties follow:
- A breach of your endpoint cannot forge events. If someone reads your endpoint configuration, they cannot fabricate events. There is no shared secret to steal.
- Key rotation needs no coordination. The platform publishes a new verification key with an overlap period. You fetch and cache keys; you need not be notified or reconfigured.
The platform retries delivery with bounded exponential backoff with jitter, over a retry window measured in hours. If an endpoint fails permanently, the event is retained for manual redelivery.
A permanently failing endpoint must not silently lose events. They remain retrievable in your console, and you are notified through a separate channel that does not depend on the failing endpoint.
Consumer obligations
Section titled “Consumer obligations”When you receive a webhook:
- Verify the signature over the raw body before parsing.
- Reject requests outside the replay window.
- Deduplicate by event identifier. Process the same event ID only once, even if it is redelivered.
- Tolerate unknown fields and unknown event types. Ignore fields and event types you do not recognize.
- Order by aggregate version if order matters to you. Do not assume arrival order.
- Respond promptly and process asynchronously. A slow consumer is indistinguishable from a failing one. Respond with
200immediately, then process the event in the background. - Treat the event as a notification, not data. When you receive a
certificate.issuedevent, learn that a certificate was issued. Fetch the certificate details by calling the API under your own authorization. - Never treat absence of an event as evidence. If you never receive a
delivery.retiredevent, reconcile by querying, not by assuming nothing happened.
Event catalogue
Section titled “Event catalogue”Events are organized by entity type. All are first-slice unless marked [2] for second slice (signing).
Organization and application
Section titled “Organization and application”| Event | Emitted when | Key fields |
|---|---|---|
organization.submitted |
Verification evidence supplied | organization_id |
organization.verified |
Legal entity confirmed | organization_id |
organization.rejected |
Verification refused | organization_id, reason |
organization.suspended |
Authority withheld | organization_id, reason |
organization.terminated |
Terminal state reached | organization_id, reason |
organization.verification_expiring |
Verification approaching lapse | organization_id, expires_at |
capability.granted |
Authority granted | organization_id, capability |
capability.revoked |
Authority withdrawn | organization_id, capability |
application.registered |
Workload registered | application_id, organization_id |
application.disabled |
Workload disabled | application_id, organization_id |
application.reinstated |
Disabled application restored | application_id, organization_id |
client_certificate.issued |
Workload identity issued | application_id, certificate_id |
client_certificate.expiring |
Approaching expiry — the most valuable operational event | application_id, certificate_id, expires_at |
client_certificate.rotated |
Replacement active | application_id, old_certificate_id, new_certificate_id |
client_certificate.revoked |
Withdrawn | application_id, certificate_id |
Subscriber and enrollment
Section titled “Subscriber and enrollment”| Event | Emitted when | Key fields |
|---|---|---|
subscriber.created |
New subscriber record | subscriber_id |
enrollment.created |
Enrollment opened | enrollment_id, subscriber_id |
enrollment.submitted |
Submitted for verification | enrollment_id, subscriber_id |
enrollment.information_requested |
Further detail demanded | enrollment_id, subscriber_id, requested_information |
enrollment.verified |
Identity evidence accepted | enrollment_id, subscriber_id |
enrollment.recommended |
Recommended by the requesting organization | enrollment_id, subscriber_id |
enrollment.approved |
Approved | enrollment_id, subscriber_id |
enrollment.rejected |
Refused, with reasons | enrollment_id, subscriber_id, rejection_reasons |
enrollment.confirmation_required |
Subject details ready for subscriber confirmation before issuance | enrollment_id, subscriber_id, proposed_certificate_digest |
enrollment.confirmed |
Subscriber confirmed the details | enrollment_id, subscriber_id |
enrollment.decision_overdue |
The statutory decision window elapsed without a decision. Enrollment remains open. | enrollment_id, subscriber_id, due_at |
enrollment.cancelled |
Withdrawn | enrollment_id, subscriber_id, reason |
About enrollment.decision_overdue: This event tells you a decision is late, not that the enrollment terminated. The decision remains owed, is recorded as late, and carries no reasons (a timer did not make the decision; a human must).
Certificate
Section titled “Certificate”| Event | Emitted when | Key fields |
|---|---|---|
certificate.issued |
Issued | certificate_id, subscriber_id, status |
certificate.published |
Published to the repository | certificate_id |
certificate.submitted_to_regulator |
Submitted to the regulator’s record | certificate_id |
certificate.submission_overdue |
Issued but not submitted past the defined threshold | certificate_id, due_at |
certificate.held |
Placed on hold pending the signed application | certificate_id, expires_at |
certificate.hold_released |
Hold lifted; certificate is now usable | certificate_id |
certificate.hold_expiring |
Approaching the hold limit without the signed form | certificate_id, expires_at |
certificate.withdrawn |
Hold limit reached; revoked as unsatisfied requirement | certificate_id, reason |
certificate.compromise_reported |
A compromise report received | certificate_id, subscriber_id |
certificate.explanation_requested |
Statutory explanation window opened | certificate_id, due_at |
certificate.suspended |
Suspended, with ground | certificate_id, subscriber_id, reason, suspended_at, release_by, revoke_by |
certificate.suspension_released |
Released | certificate_id, subscriber_id |
certificate.suspension_expiring |
Approaching the statutory maximum | certificate_id, expires_at |
certificate.show_cause_issued |
Ground for suspension established; subscriber has three days to show cause. Certificate stays suspended. | certificate_id, show_cause_until |
certificate.suspension_maximum_breached |
Rule 33(4) maximum reached on a controller-directed suspension the platform cannot release. Recorded as a defined state for an open policy contradiction. | certificate_id, due_at |
certificate.revoked |
Revoked, with reason and effective time | certificate_id, subscriber_id, reason, effective_at |
certificate.expired |
Validity period ended | certificate_id, expired_at |
certificate.expiring |
Approaching expiry | certificate_id, expires_at |
Delivery, transfer, and reissuance
Section titled “Delivery, transfer, and reissuance”| Event | Emitted when | Key fields |
|---|---|---|
delivery.created |
Delivery authorized | delivery_id, certificate_id, subscriber_id, application_id |
delivery.available |
Package retrievable | delivery_id, certificate_id |
delivery.retrieved |
Package collected | delivery_id, certificate_id, attempt |
delivery.import_confirmed |
Import confirmed with digest and thumbprint | delivery_id, certificate_id, digest_confirmed |
delivery.retired |
Material retired, destruction evidence recorded | delivery_id, certificate_id |
delivery.expired |
Window elapsed unused | delivery_id, certificate_id |
delivery.cancelled |
Withdrawn | delivery_id, certificate_id, reason |
delivery.attempt_limit_reached |
Attempts exhausted | delivery_id, certificate_id |
transfer.requested |
Transfer request opened | transfer_id, certificate_id, target_application_id |
transfer.refused |
Relay refused (revoked certificate or unauthorized target) | transfer_id, certificate_id, reason |
transfer.completed |
Reported complete by receiving application | transfer_id, certificate_id, target_application_id |
reissuance.requested |
Reissuance sought | reissuance_id, certificate_id, subscriber_id, reason |
reissuance.completed |
Successor issued, predecessor revoked | reissuance_id, old_certificate_id, new_certificate_id |
Signing
Section titled “Signing”| Event | Emitted when | Key fields |
|---|---|---|
signature_request.created [2] |
Request registered | signature_request_id, subscriber_id, organization_id |
signature_request.presented [2] |
Document presented, digest recorded | signature_request_id, subscriber_id, document_digest |
signature_request.authorized [2] |
Authorization evidence obtained | signature_request_id, subscriber_id |
signature_request.authorization_failed [2] |
Challenge unanswered, incorrect, or sent to unregistered channel | signature_request_id, subscriber_id, reason |
contact_channel.changed |
Registered authorization channel was changed | subscriber_id, channel_type |
signature_request.signed [2] |
Signature produced | signature_request_id, subscriber_id, certificate_id |
signature_request.completed [2] |
Validation material attached | signature_request_id, subscriber_id |
signature_request.declined [2] |
Subscriber refused — evidence, not absence | signature_request_id, subscriber_id |
signature_request.expired [2] |
Window elapsed | signature_request_id, subscriber_id |
signature_request.failed [2] |
Technical failure, retryable | signature_request_id, subscriber_id, reason |
envelope.participant_completed [2] |
One participant of a multi-party envelope signed | envelope_id, subscriber_id |
envelope.completed [2] |
All participants signed | envelope_id |
envelope.invalidated [2] |
Document revised; prior signatures void | envelope_id |
Delegation
Section titled “Delegation”| Event | Emitted when | Key fields |
|---|---|---|
delegation_certificate.issued [2] |
Licence certificate issued | delegation_certificate_id, organization_id |
delegation_certificate.expiring [2] |
Approaching expiry | delegation_certificate_id, expires_at |
delegation_certificate.rotated [2] |
Replacement active | delegation_certificate_id, old_id, new_id |
delegation_certificate.suspended [2] |
Licence suspended | delegation_certificate_id |
delegation_certificate.revoked [2] |
Licence revoked | delegation_certificate_id |
| Event | Emitted when | Key fields |
|---|---|---|
usage.recorded |
A billable operation occurred | organization_id, operation, period |
Usage events never gate trust operations. They record what happened; they do not authorize it. An outage in usage recording must not block issuance, status publication, or signing.
What is deliberately absent
Section titled “What is deliberately absent”These are not emitted as events:
| What | Why |
|---|---|
| Private keys, packages, or passwords | Credential material never leaves its delivery path |
| Identity evidence | Evidence is referenced by ID, never distributed |
| One-time codes | Codes are never logged or transmitted outside their delivery channel |
| Document content or presentation images | The platform never holds documents |
| Event for every revocation status check | Audit records these; emitting them would swamp consumers |
Best practices for event handling
Section titled “Best practices for event handling”1. Idempotent processing. Since events may be redelivered, handle them idempotently. Store the event id and check it before processing to avoid duplicate work.
2. Fast webhook responses. Respond with 200 immediately, then process the event asynchronously. A slow consumer looks like a broken one.
3. Fetch details on demand. Events carry identifiers; fetch full details by calling the API under your own authorization. This keeps authorization at the interface.
4. Order by aggregate version. If order matters, use the aggregate.version field to detect out-of-order delivery and reorder if needed.
5. Monitor event lag. Track when events are emitted versus when you process them. Event lag indicates a problem with your endpoint or processing.
6. Preserve key events. Events like client_certificate.expiring are operationally critical. Log them and alert on them.
7. Do not rely on event presence. Use events to trigger actions, but reconcile by querying when events matter. An integrator that waits for a delivery.retired event instead of checking the delivery status will miss events and get stuck.
See Concepts for entity definitions, Errors for error handling, and the API Reference for the complete webhook and event operations.