Skip to content

Webhook

Webhooks allow you to receive real-time notifications from Locize when important events occur in your project. This enables automation, integration, and monitoring for your localization workflow.


How to Enable Webhooks

  1. Go to your Locize project Settings.
  2. Select the API, CDN, NOTIFICATIONS tab.
  3. Configure your webhook endpoint URL (must be HTTPS for security).
  4. Optionally add custom headers, for example a secret token that your endpoint checks.
  5. Save your changes. Locize immediately sends a dummyTestEvent to the URL; the configuration is only saved when your endpoint answers with a 2xx status code.

Custom headers

The webhook dialog has a Headers field that takes a JSON object. Every header in it is sent with every delivery, including the test event Locize sends when you save the configuration:

{
  "X-Webhook-Token": "a-long-random-secret"
}

Use this to authenticate deliveries: generate a random token, put it in a header here and let your endpoint reject every request that does not carry it. Locize stores the headers encrypted and only sends them to the configured URL over HTTPS. Every delivery is also signed (see Verifying deliveries); the header check is the simpler alternative when you do not want to verify signatures.

// Express example
app.post('/locize-webhook', express.json(), (req, res) => {
  if (req.get('X-Webhook-Token') !== process.env.LOCIZE_WEBHOOK_TOKEN) return res.sendStatus(401)
  res.sendStatus(204) // acknowledge first, process the event afterwards
  // ...handle req.body
})

Verifying deliveries

Every delivery, including the test event, carries the header X-Locize-Event-Signature. Its value is a signed token (a JWT, algorithm ES256) that ties the request to Locize and to the exact body you received:

ClaimMeaning
issalways locize
subthe project id (meta.project.id)
jtithe message id (id in the body)
iat, expissued at, valid for 5 minutes
body_sha256base64url-encoded SHA-256 of the raw request body

The public keys are published as a JSON Web Key Set at https://api.locize.app/documentation/webhook-signing-keys.json. Keys can rotate; the token names its key in the kid header, so verify through that URL instead of pinning a key.

To verify a delivery: read the raw body bytes before parsing them, verify the token against the key set (issuer locize, algorithm ES256), then compare body_sha256 with the hash of the raw body. Any JWT library that supports JWKS does the first part.

// Node.js (Express) with jose
import { createRemoteJWKSet, jwtVerify } from 'jose'
import { createHash } from 'node:crypto'

const JWKS = createRemoteJWKSet(new URL('https://api.locize.app/documentation/webhook-signing-keys.json'))

app.post('/locize-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    const { payload } = await jwtVerify(req.get('X-Locize-Event-Signature'), JWKS, { issuer: 'locize', algorithms: ['ES256'] })
    if (payload.body_sha256 !== createHash('sha256').update(req.body).digest('base64url')) throw new Error('body hash mismatch')
  } catch (err) {
    return res.sendStatus(401)
  }
  res.sendStatus(204) // acknowledge first, process the event afterwards
  const event = JSON.parse(req.body)
  // ...handle event
})
# Python with PyJWT (pip install "pyjwt[crypto]")
import base64, hashlib, jwt

jwks = jwt.PyJWKClient("https://api.locize.app/documentation/webhook-signing-keys.json")

def verify(raw_body: bytes, token: str) -> dict:
    key = jwks.get_signing_key_from_jwt(token)
    claims = jwt.decode(token, key, algorithms=["ES256"], issuer="locize")
    digest = base64.urlsafe_b64encode(hashlib.sha256(raw_body).digest()).rstrip(b"=").decode()
    if claims["body_sha256"] != digest:
        raise ValueError("body hash mismatch")
    return claims
// C# with Microsoft.IdentityModel.JsonWebTokens
var jwks = new JsonWebKeySet(await http.GetStringAsync("https://api.locize.app/documentation/webhook-signing-keys.json"));
var result = await new JsonWebTokenHandler().ValidateTokenAsync(token, new TokenValidationParameters
{
    ValidIssuer = "locize",
    ValidateAudience = false,
    IssuerSigningKeys = jwks.GetSigningKeys()
});
if (!result.IsValid) return Results.Unauthorized();
var digest = Base64UrlEncoder.Encode(SHA256.HashData(rawBody));
if ((string)result.Claims["body_sha256"] != digest) return Results.Unauthorized();

The hash covers the exact bytes Locize sent, so read the body before any JSON parsing or re-serialisation (ASP.NET: Request.EnableBuffering(); Express: express.raw() or the verify callback of express.json()). Allow a little clock tolerance if your servers drift; every JWT library has a leeway option. A secret token in a custom header keeps working as a simpler alternative or in addition.


Best Practices

  • Use HTTPS for your webhook endpoint.
  • Verify the X-Locize-Event-Signature header on every delivery, see Verifying deliveries; a secret token in a custom header is the simpler alternative.
  • Respond immediately with a 2xx status code (e.g. 204) and process the event asynchronously afterwards. Locize waits up to 35 seconds, but gateways in between often time out earlier (e.g. AWS API Gateway answers 504 after 29 seconds, which counts as a failed delivery).
  • Log received events for troubleshooting.
  • If your endpoint fails repeatedly (non-2xx status), Locize automatically disables the webhook, see automatic deactivation.

Event Types

Event NameDescription
languageAddedA language was added to the project
languageDeletedA language was removed from the project
versionAddedA new version was created
versionDeletedA version was deleted
referenceLanguageChangedThe reference language was changed
orderCreatedA translation order was created
orderCompletedA translation order was completed
invitationAcceptedA user accepted an invitation to join the project
versionPublishedA version was published
versionOverwroteA version was overwritten
languageOverwroteA language was overwritten
namespaceAddedA namespace was added
namespaceDeletedA namespace was deleted
namespaceUpdatedA namespace was updated
namespaceCompletedA namespace translation was completed
namespaceNotCompletedAnymoreA fully translated namespace received new untranslated segments (sent per language)
needsReviewThresholdA language's review queue crossed the threshold set in the project settings (sent per language and version, once per crossing)

Get directly notified on a webhook url if something noticeable happens.

To enable this service go to settings, select the services tab and configure your endpoint. Make sure you have a secure https endpoint.

The following messages will be sent as json payload with a POST request.

All messages will have the following structure:

{
   "id":"830fbcbb-90b7-4f0f-86bb-c82b55aab385", // the message id
   "name":"dummyTestEvent", // the message name
   "occurredAt":"2018-01-02T20:05:59.008Z", // the moment when this message occurred in ISO 8601 format
   "message":"William Timberland just added a webhook to THIS PROJECT!", // a descriptive text of this message
   "payload":{ // a variable payload
      "messageTypes":[
         "languageAdded",
         "languageDeleted",
         "versionAdded",
         "versionDeleted",
         "referenceLanguageChanged",
         "orderCreated",
         "orderCompleted",
         "invitationAccepted",
         "versionPublished",
         "versionOverwrote",
         "languageOverwrote",
         "namespaceAdded",
         "namespaceDeleted",
         "namespaceCompleted",
         "namespaceNotCompletedAnymore",
         "needsReviewThreshold"
      ]
   },
   "meta":{ // a meta object containing...
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6", // the project id
         "name":"THIS PROJECT", // the project name
         "slug":"albo6dxk" // the project slug
      },
      "user":{ // if message issued by a user
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a", // the user id
         "firstname":"William", // the user firstname
         "lastname":"Timberland" // the user lastname
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest" // a link pointing to the locize client
   }
}

Automatic deactivation

Every delivery expects a 2xx response. Deliveries that fail count as strikes:

  1. When your endpoint answers with a non-2xx status code, the failure is recorded on the webhook (first strike).
  2. If the next delivery also fails, the webhook is disabled automatically (second strike).
  3. A successful delivery in between clears the recorded failure, so only two consecutive failures deactivate the webhook.

An automatically disabled webhook is shown as switched off in your project settings under Notifications, with an info icon next to it revealing the last error (e.g. Got a "504" http status code, but expecting "2xx").

To recover: fix your endpoint, then re-enable the webhook in the settings. Re-enabling resets the failure counter.

Tip: The most common cause is a timeout in front of your endpoint (e.g. AWS API Gateway returns 504 after 29 seconds). Acknowledge the delivery immediately with a 2xx and do the actual processing asynchronously.


Example Events

languageAdded

Triggered when a new language is added to the project. versions lists the versions the language was added to, and the event can be filtered by version in the webhook settings.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"languageAdded",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland added language de!",
   "payload":{
      "language":"de",
      "versions":["latest", "prod"]
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

languageDeleted

Triggered when a language is removed from the whole project (removing it from only some versions does not send this event). versions lists the versions that held the language, and the event can be filtered by version in the webhook settings.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"languageDeleted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland removed language de!",
   "payload":{
      "language":"de",
      "versions":["latest", "prod"]
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

versionAdded

Triggered when a new version is created.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"versionAdded",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland added version prod!",
   "payload":{
      "version":"prod"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

versionDeleted

Triggered when a version is deleted.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"versionDeleted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland removed version prod!",
   "payload":{
      "version":"prod"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

referenceLanguageChanged

Triggered when the reference language changes.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"referenceLanguageChanged",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland changed reference language from en to de!",
   "payload":{
      "from":"en",
      "to":"de"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

orderCreated

Triggered when a translation order is created.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"orderCreated",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland created a translation order with gengo!",
   "payload":{
      "service":"gengo",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

orderCompleted

Triggered when a translation order is completed.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"orderCompleted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"gengo order completed!",
   "payload":{
      "service":"gengo",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "link":"https://www.locize.app/p/albo6dxk/orders"
   }
}

invitationAccepted

Triggered when a user accepts an invitation.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"invitationAccepted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland accepted the invitation and is now part of your project!",
   "payload":{},
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

versionPublished

Triggered when a version is published.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"versionPublished",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland published version prod!",
   "payload":{
      "version":"prod"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/prod"
   }
}

The versionPublished event will not be triggered when auto publish is enabled. For reacting to new translations, consider namespaceNotCompletedAnymore and namespaceCompleted.

versionOverwrote

Triggered when a version is overwritten.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"versionOverwrote",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland overwrote version prod with test!",
   "payload":{
      "from":"test",
      "to":"prod"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/prod"
   }
}

languageOverwrote

Triggered when a language is overwritten.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"languageOverwrote",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland overwrote language en version in version prod with test!",
   "payload":{
      "from":"test",
      "to":"prod",
      "language":"en"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/prod"
   }
}

namespaceAdded

Triggered when a namespace is added.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"namespaceAdded",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland added namespace landingpage in version latest!",
   "payload":{
      "namespace":"landingpage",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

namespaceDeleted

Triggered when a namespace is deleted.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"namespaceDeleted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland removed namespace landingpage in version latest!",
   "payload":{
      "namespace":"landingpage",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest"
   }
}

namespaceUpdated

Triggered when a namespace is updated.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"namespaceUpdated",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"William Timberland updated namespace for language en landingpage in version latest!",
   "payload":{
      "namespace":"landingpage",
      "language":"en",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "user":{
         "id":"ab075147-c7a3-45d2-9b24-739ae2ed948a",
         "firstname":"William",
         "lastname":"Timberland"
      },
      "link":"https://www.locize.app/cat/albo6dxk/v/latest/en/landingpage"
   }
}

namespaceCompleted

Triggered when a namespace translation is completed.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"namespaceCompleted",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"Translation of namespace landingpage in language en for version latest completed!",
   "payload":{
      "namespace":"landingpage",
      "language":"en",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest/en/landingpage"
   }
}

namespaceNotCompletedAnymore

Triggered when new segments in a namespace need translation: a namespace that was 100 % translated in a language dropped below 100 %, typically because new keys were added. It is sent once per language and namespace for that transition, not for every new key.

{
   "id":"427cc4dd-adf5-4fb8-ad8d-8508f2788fe3",
   "name":"namespaceNotCompletedAnymore",
   "occurredAt":"2018-01-02T21:27:11.326Z",
   "message":"New segments of namespace landingpage ready to be translated in language en for version latest!",
   "payload":{
      "namespace":"landingpage",
      "language":"en",
      "version":"latest"
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "link":"https://www.locize.app/p/albo6dxk/v/latest/en/landingpage"
   }
}

needsReviewThreshold

Triggered when the number of segments needing review in a language crosses the threshold set in the project settings (Review section). Sent once per crossing; it is sent again only after the queue dropped below the threshold and crossed it once more.

{
   "id":"6a1f0c2e-3b7d-4e0a-9c11-2f5d8e7a4b90",
   "name":"needsReviewThreshold",
   "occurredAt":"2026-09-07T09:12:44.118Z",
   "message":"64 segments need review in language de for version latest!",
   "payload":{
      "language":"de",
      "version":"latest",
      "count":64
   },
   "meta":{
      "project":{
         "id":"23dad587-b3bf-4663-b15c-ad8d66213ac6",
         "name":"THIS PROJECT",
         "slug":"albo6dxk"
      },
      "link":"https://www.locize.app/cat/albo6dxk/v/latest#lng=de&state=2"
   }
}

Not covered by this event: a change of the source text of an existing key. The translation still exists, so the namespace stays "completed"; the editor marks such segments as needing review (and flags them as issue 302), needsReviewThreshold tells you when that queue grows beyond your threshold, and a translation order picks them up with "Include if source value changed after target value". With Automatic Translation enabled the event is rarely sent, because new keys are already translated when the statistics are recalculated; use namespaceUpdated, orderCreated and orderCompleted for automations in that setup.