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": { } }
idis stable across retries and replays: de-duplicate on it (headerRL-Event-Id). Delivery is at least once and not ordered: two events from one job can arrive in either order, and a retry of an old event can arrive after a newer one. Usecreated(second resolution) andid, not arrival order. After a success the same id is never sent again unless you press replay.- The tenant id is not included.
siteIdis the site the event is about, or null for account-level events; an endpoint scoped to a site receives only events with thatsiteId. - Retries: non-2xx, timeout or connection error is retried (about 30 s, 2 min, 10 min, 30 min, 2 h, 6 h, 12 h, +/-20%), 8 attempts, redirects count as failures, 410 disables the endpoint. See
WEBHOOKS.md. - No text, no secrets: payloads carry ids, codes, counts and language codes, never page text, translation text, URLs of customer pages, tokens or keys. Keys and values that look like secrets (
secret,token,password,authorization,api_key,signature,cookie, ...;rl_sec_...,whsec_...,Bearer ...) are replaced by[redacted], string values are cut at 512 characters, and the whole body is limited to 64 KiB. - Audit log: every event except
webhook.pingis also written to the tenant's audit log under the same name, with the scalardatamembers only (arrays such aslanguagesandrecipientUserIdsare left out of the audit metadata). - Publishing is fail-soft for the callers (a failing publisher never fails a job, an edit, an install or a Shopify webhook).
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 key | Type | Meaning |
|---|---|---|
version | integer | Manifest version that is now live. |
languages | array of string | Language 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 key | Type | Meaning |
|---|---|---|
jobId | string (GUID) | The job, as returned by GET /v1/jobs/{jobId}. |
kind | string | scan, translate or publish. |
code | string | Short machine code: suspended, licence_lapsed, quota_*, origin_not_allowed, engine_<kind>, invalid, error. |
reason | string | One 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 key | Type | Meaning |
|---|---|---|
reason | string | quota_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 key | Type | Meaning |
|---|---|---|
keyId | string (GUID) | Id of the new key (the one just issued), not the old one. |
keyPrefix | string | Display prefix of the new key (first characters only), never the key itself. |
oldKeyValidUntil | string (ISO 8601 UTC) or null | When 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 key | Type | Meaning |
|---|---|---|
lang | string | Target language. |
key | string | Source key (hash), never the text. |
previousStatus | string or null | Single save only: the status before (Machine, Reviewed, Manual, or null). |
length | integer or null | Single save only: length of the new text in characters, not the text. |
changed | integer | Bulk only: rows changed. |
skipped | integer | Bulk only: rows skipped. |
jobId | string or null | Bulk 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 key | Type | Meaning |
|---|---|---|
jobId | string (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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source key. |
commentId | string (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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
key | string | Source 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
invitationId | string (GUID) | The invitation. |
role | string | Translator 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
invitationId | string (GUID) | The invitation. |
role | string | Translator 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
invitationId | string (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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
lang | string | Target language. |
change | string | assigned or removed. |
role | string | Translator 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also tell owners and admins. |
jobId | string (GUID) | The queued Publish job. |
approved | string | Number 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 key | Type | Meaning |
|---|---|---|
recipientUserIds | array of string (GUID) | Users who should be told; may be empty. |
alsoNotifyManagers | boolean | Also 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 key | Type | Meaning |
|---|---|---|
shop | string | The store's myshopify domain. |
reinstall | boolean | True 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 key | Type | Meaning |
|---|---|---|
shop | string | The 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 key | Type | Meaning |
|---|---|---|
message | string | Fixed 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#
- Jobs sends
site.publishedwith onlyversionandlanguages,job.failedwithcode/reason(no attempt count) andquota.reachedwith onlyreason;EventPayloadsnow matches (earlier drafts promisedtargetsOk,metric,limit,used). publish.requestedsendsapprovedas a string;translation.editedhas two shapes (single save, bulk).job.failed.reasonfor refused jobs is the exception message of the refusal (JobsJobBlockedException), which is a customer-facing sentence by design; Events cuts it at 512 characters and redacts secrets as a second line of defence.