Webhooks

Event reference

Look up every webhook event, when it fires and the exact payload your endpoint receives.

An endpoint receives only the events it subscribed to. The event name arrives in the X-Transglot-Event header and again as event in the body.

EventFires whenDelivery
batch.completedA translation run finished: completed or completed_with_errorsOnce per run
batch.failedA translation run ended failed or cancelledOnce per run
key.createdKeys were createdCoalesced, 30 s
key.updatedKeys changed name, description, plural flag or length limitCoalesced, 30 s
key.deletedKeys were deletedCoalesced, 30 s
translation.updatedTranslation values were writtenCoalesced, 15 s
translation.reviewedTranslations were approvedCoalesced, 30 s
language.addedA target language was addedOnce
language.removedA target language was removedOnce
glossary.updatedGlossary terms changedCoalesced, 30 s
qa.completedA quality check finished scoring translationsCoalesced, 60 s
source_drift.repairedTranslations were repaired after their source changedCoalesced, 60 s

The envelope

Every payload carries these fields:

Prop

Type

The examples on this page are formatted for reading. The body you receive is compact JSON and its keys can come in a different order, so read fields by name.

Coalesced events

A coalesced event is not delivered once per occurrence. The first occurrence opens a window (15, 30 or 60 seconds, depending on the event). Every further occurrence in that window adds to a counter. When the window closes, your endpoint receives one delivery for the whole window, and the next occurrence opens a new window.

Prop

Type

samples is for display and logging. To act on exactly what changed, use the event as a trigger and fetch the current state, for example with GET /v1/pull or POST /v1/translations/batch.

batch.completed

A translation run reached a successful final state: completed, or completed_with_errors when some pairs failed. Delivered exactly once per run, whatever started it (a push, the console, POST /v1/translate, a language being added, a retry).

batch has the same shape as GET /v1/batches/{batch}.

batch.completed
{
  "event": "batch.completed",
  "delivered_at": "2026-10-12T09:41:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "batch": {
    "id": 4821,
    "status": "completed",
    "trigger": "push",
    "total": 240,
    "completed": 240,
    "failed": 0,
    "skipped": 12,
    "skipped_reasons": { "human_value_stale": 12 },
    "tm_served": 37,
    "language_breakdown": { "fr": 120, "de": 120 },
    "started_at": "2026-10-12T09:39:13+00:00",
    "finished_at": "2026-10-12T09:41:00+00:00"
  }
}

Prop

Type

batch.failed

A translation run ended failed, or was cancelled from the console or with POST /v1/batches/{batch}/cancel. Delivered exactly once per run, with the same payload shape as batch.completed.

batch.failed
{
  "event": "batch.failed",
  "delivered_at": "2026-10-12T09:41:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "batch": {
    "id": 4821,
    "status": "failed",
    "trigger": "push",
    "total": 240,
    "completed": 191,
    "failed": 49,
    "skipped": 0,
    "skipped_reasons": null,
    "tm_served": 37,
    "language_breakdown": { "fr": 120, "de": 120 },
    "started_at": "2026-10-12T09:39:13+00:00",
    "finished_at": "2026-10-12T09:41:00+00:00"
  }
}

To see which pairs failed and why, call GET /v1/batches/{batch}?include=failures.

key.created

Keys were created in the console or by a source-locale push. Coalesced over 30 seconds. count is the number of keys created; samples holds key names, and push:<locale> for a push.

key.created
{
  "event": "key.created",
  "delivered_at": "2026-10-12T09:50:31+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 14,
  "samples": ["push:en"],
  "window_seconds": 30
}

key.updated

Keys changed name, description, plural flag or length limit: an edit in the console, a bulk rename (sample bulk-rename), or a source push whose metadata sidecar changed descriptions or length limits (sample push-metadata:<locale>). Coalesced over 30 seconds.

key.updated
{
  "event": "key.updated",
  "delivered_at": "2026-10-12T10:12:30+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 2,
  "samples": ["checkout.submit", "checkout.error.declined"],
  "window_seconds": 30
}

key.deleted

Keys were deleted: one at a time in the console (sample: the key name), in bulk (sample bulk), or by a source push with mode=sync_removals (sample push:<locale>). Coalesced over 30 seconds.

key.deleted
{
  "event": "key.deleted",
  "delivered_at": "2026-10-12T10:20:04+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 31,
  "samples": ["bulk"],
  "window_seconds": 30
}

translation.updated

Translation values were written. Coalesced over 15 seconds. count counts written values only: a pair a translation run left alone, because a person had already edited or reviewed it or because its source moved during the run, writes nothing and is not counted.

Source of the writeSample
An edit in the console editor<key>:<locale>, for example checkout.submit:fr
A push (source or target)push:<locale>
A translation runbatch:<batch id>:<locale>
Copying the source into a locale in bulkbulk-copy-source:<locale>
translation.updated
{
  "event": "translation.updated",
  "delivered_at": "2026-10-12T09:41:12+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 240,
  "samples": ["batch:4821:fr", "batch:4821:de"],
  "window_seconds": 15
}

Writes through the API

A value written with PUT /v1/translations does not fire translation.updated. If your own integration writes values that way, you already know about the change.

translation.reviewed

Translations were approved in the review workflow. Coalesced over 30 seconds. samples holds <key>:<locale> for a single approval and bulk:<locale> for a bulk approval.

translation.reviewed
{
  "event": "translation.reviewed",
  "delivered_at": "2026-10-12T11:02:45+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 37,
  "samples": ["checkout.submit:fr", "bulk:de"],
  "window_seconds": 30
}

language.added

A target language was added to the project. Delivered once, as soon as the language exists. A translation run to fill it may follow and reports through the batch.* events.

language.added
{
  "event": "language.added",
  "delivered_at": "2026-10-12T12:00:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "language": { "locale": "pt-BR" }
}

language.removed

A target language was removed from the project. Every translation in that locale and its version history were deleted with it, and neither can be recovered through the product. Delivered once. React to it by dropping the locale from your builds, your CDN configuration or your CMS.

language.removed
{
  "event": "language.removed",
  "delivered_at": "2026-10-12T12:30:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "language": { "locale": "pt-BR", "translations_deleted": 1204 }
}

language.translations_deleted tells you whether the locale held content or was an empty placeholder.

glossary.updated

Glossary terms were created, edited, approved, rejected, imported or deleted in the console. Coalesced over 30 seconds. samples holds source terms, and import for a file import, whose count is the number of terms it created or updated.

glossary.updated
{
  "event": "glossary.updated",
  "delivered_at": "2026-10-12T13:05:30+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 3,
  "samples": ["Checkout", "Acme"],
  "window_seconds": 30
}

Changes made through the glossary API endpoints do not fire this event.

qa.completed

A quality check finished scoring translations. A check is processed in groups, and the 60-second window collapses a whole check into one delivery. count is the number of translations checked. samples holds flagged <key>:<locale> pairs, at most one per group, so an empty samples means nothing was flagged. The findings themselves are on the translations: read them with GET /v1/translations or in the editor.

qa.completed
{
  "event": "qa.completed",
  "delivered_at": "2026-10-12T14:01:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 74,
  "samples": ["checkout.error.declined:de"],
  "window_seconds": 60
}

source_drift.repaired

Source strings changed, and the translations that depended on them were checked again and repaired, either as a suggestion or applied directly, according to the project's source-drift setting. Coalesced over 60 seconds. count is the number of translations repaired; samples holds <key>:<locale> pairs.

source_drift.repaired
{
  "event": "source_drift.repaired",
  "delivered_at": "2026-10-12T15:10:00+00:00",
  "project": { "id": 12, "slug": "acme-web" },
  "count": 6,
  "samples": ["checkout.submit:fr", "checkout.submit:de"],
  "window_seconds": 60
}

On this page