ResilientLang docsDeveloper documentation (draft, product name is a placeholder)

Event reference

This page is generated at build time from docs/events/EVENTS.md in the repository, the reference written from the publishers' code. A test in the Events module fails when an event type is added without a section there; a test here fails when a type is missing from this page. How to receive, verify and replay events is on the Webhooks page.

Reference for every event type (ResilientLang.Contracts.EventTypes). Written from the publishers' code on 2026-10-28; a test (EventsDocTests in tests/ResilientLang.Events.Tests) fails when a type is added to EventTypes without a section here, when a section's example does not parse or contradicts its heading, or when an example data key looks like a secret. Delivery, signatures and the audit log are described in src/ResilientLang.Events/docs/WEBHOOKS.md.

Envelope (all events)#

{ "id": "evt_...", "type": "site.published", "created": "2026-10-28T12:00:00Z", "siteId": "<guid or null>", "data": { } }

site.published#

Raised by: ResilientLang.Jobs, PublishJobHandler (after the bundles are on every target and the manifest is recorded). Actor: system.

When it fires: a Publish job finished successfully. A publish skipped because nothing changed (onlyIfChanged with the same content hash) raises nothing.

data keyTypeMeaning
versionintegerManifest version that is now live.
languagesarray of stringLanguage codes in the published manifest, e.g. ["fr","de"].
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "site.published",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "version": 7,
    "languages": [
      "fr",
      "de"
    ]
  }
}

A failed or partial publish raises job.failed, not this event.

job.failed#

Raised by: ResilientLang.Jobs, JobRunner.FailedEventAsync (scan, translate and publish jobs). Actor: system.

When it fires: a job will not run again: it was refused (suspended, licence, allowance, origin), the provider refused it, or it failed after its last attempt.

data keyTypeMeaning
jobIdstring (GUID)The job, as returned by GET /v1/jobs/{jobId}.
kindstringscan, translate or publish.
codestringShort machine code: suspended, licence_lapsed, quota_*, origin_not_allowed, engine_<kind>, invalid, error.
reasonstringOne sentence safe to show to the customer (at most 512 characters, secrets removed). Never page text, URLs or provider responses.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "job.failed",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "jobId": "0b6f1c52-8d3e-4a77-9c10-2e5d7a9b3f41",
    "kind": "translate",
    "code": "engine_quotaexceeded",
    "reason": "The translation provider refused the request (QuotaExceeded)."
  }
}

Retries that will still run do not raise it; only the final failure does.

quota.reached#

Raised by: ResilientLang.Jobs, JobGates.QuotaReachedAsync (called when IQuotaGate denies a job and when a provider reports its quota exceeded). Actor: system.

When it fires: a job was stopped because the allowance is used up or a cap applies.

data keyTypeMeaning
reasonstringquota_exhausted, quota_strings_cap, quota_daily_ceiling, quota_machine_translation_disabled or quota_denied.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "quota.reached",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "reason": "quota_exhausted"
  }
}

There is no limit/used figure in the payload; read GET /v1/sites/{siteId}/usage. One event per refused job, so a busy site can raise several in a row: de-duplicate on reason and time.

key.rotated#

Raised by: ResilientLang.Dashboard, page Keys/Rotate (Rotate.cshtml.cs, after the new key is issued; a failing publisher never hides the new key from the user). Actor: the signed-in user. Not yet raised by the public API route POST /v1/keys/{id}/rotate (ResilientLang.Api/OpsEndpoints.cs, WP-08): a rotation made with a secret key through the API sends no event until WP-08 adds the call.

When it fires: a secret API key was rotated (not on creation or revocation). The event describes the new key; the key it replaces keeps working until oldKeyValidUntil.

data keyTypeMeaning
keyIdstring (GUID)Id of the new key (the one just issued), not the old one.
keyPrefixstringDisplay prefix of the new key (first characters only), never the key itself.
oldKeyValidUntilstring (ISO 8601 UTC) or nullWhen the previous key stops working, i.e. the end of the grace period chosen at rotation. null means the previous key was revoked at once (no grace period).
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "key.rotated",
  "created": "2026-10-28T12:00:00Z",
  "siteId": null,
  "data": {
    "keyId": "5c0e9b1a-2d44-4f6b-8a13-7e9d0c1b2a34",
    "keyPrefix": "rl_sec_li",
    "oldKeyValidUntil": "2026-10-29T12:00:00Z"
  }
}

Account-level: siteId is null, so site-scoped endpoints never receive it. EventPayloads.KeyRotated(tenantId, newKeyId, newKeyPrefix, oldKeyValidUntil, actor) builds this shape (decision of the integrator, 2026-10-28: the event carries the NEW key's id and prefix).

translation.edited#

Raised by: ResilientLang.Dashboard.Editor, EditorActivity via the Strings page. Actor: the signed-in user.

When it fires: a person saved a manual translation, marked machine translations reviewed, or reverted rows to untranslated. Queuing a machine re-translation raises no event from the editor.

data keyTypeMeaning
langstringTarget language.
keystringSource key (hash), never the text.
previousStatusstring or nullSingle save only: the status before (Machine, Reviewed, Manual, or null).
lengthinteger or nullSingle save only: length of the new text in characters, not the text.
changedintegerBulk only: rows changed.
skippedintegerBulk only: rows skipped.
jobIdstring or nullBulk only: always null for these actions.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "translation.edited",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8",
    "previousStatus": "Machine",
    "length": 42
  }
}

Single save sends lang,key,previousStatus,length; bulk sends lang,changed,skipped,jobId. Source and translated text are never included.

site.publish_requested#

Raised by: ResilientLang.Dashboard.Editor, Strings page "Publish". Actor: the signed-in user.

When it fires: a person asked for a publish from the editor; the Publish job is queued.

data keyTypeMeaning
jobIdstring (GUID)The queued Publish job.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "site.publish_requested",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "jobId": "0b6f1c52-8d3e-4a77-9c10-2e5d7a9b3f41"
  }
}

The result arrives later as site.published or job.failed.

review.requested#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a translator submitted a translation for review.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key (hash).
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "review.requested",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

With no reviewer assigned for the language, recipientUserIds is empty and alsoNotifyManagers is true.

review.approved#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a reviewer approved a proposed translation.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "review.approved",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

review.rejected#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a reviewer rejected a proposed translation.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "review.rejected",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

review.withdrawn#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: the proposer withdrew a pending review item.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "review.withdrawn",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

comment.added#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: someone commented on a string.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key.
commentIdstring (GUID)The comment. The text is not included.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "comment.added",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8",
    "commentId": "8e7d6c5b-4a39-4281-90fe-dcba98765432"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

comment.resolved#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a comment thread was resolved.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
keystringSource key.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "comment.resolved",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "key": "s.4f9a2c71e0b3d5a8"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

invitation.created#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a collaborator invitation was issued (the token is mailed, never sent here).

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
invitationIdstring (GUID)The invitation.
rolestringTranslator or Reviewer.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "invitation.created",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [],
    "alsoNotifyManagers": false,
    "invitationId": "c4b3a291-0f8e-4d7c-b6a5-948372615a0b",
    "role": "Translator"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

invitation.accepted#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: an invitation was accepted.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
invitationIdstring (GUID)The invitation.
rolestringTranslator or Reviewer.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "invitation.accepted",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "7d2f6f0e-1c3a-4b8d-9e21-0a5b6c7d8e9f"
    ],
    "alsoNotifyManagers": false,
    "invitationId": "c4b3a291-0f8e-4d7c-b6a5-948372615a0b",
    "role": "Reviewer"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

Raised for the tenant and site of the invitation; the actor is the person who accepted.

invitation.revoked#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: an invitation was revoked before use.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
invitationIdstring (GUID)The invitation.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "invitation.revoked",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [],
    "alsoNotifyManagers": false,
    "invitationId": "c4b3a291-0f8e-4d7c-b6a5-948372615a0b"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

assignment.changed#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a collaborator was assigned to, or removed from, a language.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
langstringTarget language.
changestringassigned or removed.
rolestringTranslator or Reviewer.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "assignment.changed",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [
      "b1c2d3e4-f5a6-4789-8abc-def012345678"
    ],
    "alsoNotifyManagers": false,
    "lang": "fr",
    "change": "assigned",
    "role": "Reviewer"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

publish.requested#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: a reviewer or manager queued a publish of approved translations (distinct from site.publish_requested, which comes from the editor).

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
jobIdstring (GUID)The queued Publish job.
approvedstringNumber of approved, unpublished items, as a string (the publisher sends a string map).
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "publish.requested",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [],
    "alsoNotifyManagers": true,
    "jobId": "0b6f1c52-8d3e-4a77-9c10-2e5d7a9b3f41",
    "approved": "12"
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

collab.settings.changed#

Raised by: ResilientLang.Collaboration, CollaborationService. Actor: the user who did it.

When it fires: an owner or admin changed the site's collaboration settings.

data keyTypeMeaning
recipientUserIdsarray of string (GUID)Users who should be told; may be empty.
alsoNotifyManagersbooleanAlso tell owners and admins.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "collab.settings.changed",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "recipientUserIds": [],
    "alsoNotifyManagers": true
  }
}

Collaboration events always carry recipientUserIds (array of user GUID strings, the actor removed) and alsoNotifyManagers (bool). They exist for the notification service (email); a customer webhook receives them too.

shopify.installed#

Raised by: integrations/shopify (ResilientLang.Shopify), ShopifyOnboarding after the installation record is saved. Actor: none recorded (system).

When it fires: a store installed or re-installed the app.

data keyTypeMeaning
shopstringThe store's myshopify domain.
reinstallbooleanTrue when the store had an installation record before.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "shopify.installed",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "shop": "example-store.myshopify.com",
    "reinstall": false
  }
}

siteId is null until the first site is created for the store, so site-scoped endpoints do not receive it. Tokens are never included. Fail-soft: a failing publisher never fails the install.

shopify.uninstalled#

Raised by: integrations/shopify (ResilientLang.Shopify), WebhookProcessor on Shopify's app/uninstalled webhook. Actor: none recorded (system).

When it fires: a store uninstalled the app.

data keyTypeMeaning
shopstringThe store's myshopify domain.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "shopify.uninstalled",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "shop": "example-store.myshopify.com"
  }
}

Fail-soft: a failing publisher never fails the Shopify webhook (Shopify would retry and eventually drop the subscription).

webhook.ping#

Raised by: ResilientLang.Events, WebhookService.SendTestAsync (POST /v1/webhooks/{id}/test). Actor: the caller.

When it fires: only when someone presses "send test event". Never subscribable; never written to the audit log.

data keyTypeMeaning
messagestringFixed text.
{
  "id": "evt_9a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "type": "webhook.ping",
  "created": "2026-10-28T12:00:00Z",
  "siteId": "3f0c2d6e-5a41-4c1e-9c57-1d2a7b8e9f10",
  "data": {
    "message": "Test event from the dashboard."
  }
}

Rate limited: 3 per minute per endpoint, 10 per minute per account.

Events with no publisher yet#

Every type has at least one publisher in the repository. key.rotated is published by the Dashboard page Keys/Rotate and by the API route POST /v1/keys/{id}/rotate (ResilientLang.Api/OpsEndpoints.cs).

Publishers: Jobs (site.published, job.failed, quota.reached), Editor (translation.edited, site.publish_requested), Collaboration (all review.*, comment.*, invitation.*, assignment.changed, publish.requested, collab.settings.changed), Dashboard and API (key.rotated), Shopify (shopify.installed, shopify.uninstalled), Events itself (webhook.ping).

Also unwired: the audit log is only written by modules that call IAuditLog (Editor does; Identity, Backoffice operator sessions, Licensing and Billing should) and by this module for the events above.

Differences found while writing this#