Integrations
An integration connects a project — or a whole workspace — to something outside Ownlate. Each one is a provider, a configuration, and a set of credentials that are encrypted before they are stored and never returned by the API.
| Category | Providers |
|---|---|
| Version control | GitHub, GitLab |
| Machine translation | OpenAI, Anthropic, Mistral, DeepL, Google, Azure — see AI translation |
| Notifications | Slack, Microsoft Teams, Discord, Mattermost, Matrix, Telegram |
| Webhooks | Generic webhook |
Managing an integration
Section titled “Managing an integration”Integrations live on the project’s Integrations tab, and workspace-wide ones under Workspace → Integrations.
| Action | What it does |
|---|---|
| Test before saving | Checks the credentials you have typed without storing them |
| Test | Checks a saved integration |
| Pause | Keeps the configuration but stops it acting |
| Activate | Turns a paused one back on |
| Sync logs | The record of each run, line by line, with its outcome |
Scope decides reach. A project integration serves one project; a workspace one is available to every project in the workspace. For machine translation, a project without its own falls back to the workspace’s. For webhooks, workspace-level hooks fire only when the project has no webhook of its own. Notifications are delivered from project-scoped integrations.
Everything an integration does is written to its sync log, including failures, so a webhook that never arrived can be told apart from one that was never sent.
Git: GitHub and GitLab
Section titled “Git: GitHub and GitLab”A version control integration takes a repository URL, a branch (defaulting to main) and an access token.
On sync, Ownlate reads ownlate.yml — or ownlate.yaml — from the root of the branch. This is the same file the CLI uses:
project_id: 00000000-0000-0000-0000-000000000000
files: - source: locales/en.json translation: locales/{lang}.jsonFor each mapping it fetches the source file from the repository and uploads it, creating and updating segments exactly as a manual upload would. A file listed in the config but missing from the repository is skipped and noted in the log; a repository with no config syncs nothing and says so.
files[].translation must contain the {lang} placeholder — it is the pattern translated files are written back to.
Sync runs in the background. Trigger it by hand from the integrations tab, or over the API with PATCH /v1/workspaces/{workspaceId}/integrations/{id}/sync.
Pull requests
Section titled “Pull requests”Create PR goes the other way: Ownlate exports the approved translations for the languages you pick, writes them to the paths from ownlate.yml, pushes a new branch and opens a pull request — a merge request on GitLab. The response carries the URL, so a pipeline can pick it up.
The token needs enough scope to read the repository, push a branch and open a pull request.
Notifications
Section titled “Notifications”Six chat destinations, each formatted in that platform’s own style — a Block Kit message on Slack, a MessageCard on Teams, and so on.
| Provider | What it needs |
|---|---|
| Slack | Incoming webhook URL |
| Microsoft Teams | Connector URL |
| Discord | Webhook URL |
| Mattermost | Webhook URL |
| Telegram | Bot token, chat ID |
| Matrix | Homeserver URL, room ID, access token |
Messages carry the key, the language, the source text and the new translation, plus a link straight to the segment in Ownlate.
Webhooks
Section titled “Webhooks”A webhook integration takes a URL and an optional secret, and posts JSON to it:
{ "event": "translation.approved", "payload": { "segmentId": "…", "projectId": "…", "workspaceId": "…", "language": "de", "key": "home.title", "sourceText": "Hello", "translationText": "Hallo", "actorId": "…", "authorId": "…", "approvedAt": "2026-08-20T09:31:00Z" }, "timestamp": "1787654321000"}Every delivery carries these headers:
| Header | Contents |
|---|---|
X-Ownlate-Event | The event name |
X-Ownlate-Timestamp | Milliseconds since the epoch, the same value as in the body |
X-Ownlate-Signature | sha256=…, present only when a secret is set |
Verifying the signature
Section titled “Verifying the signature”The signature is an HMAC-SHA256 over the timestamp, a full stop, and the raw request body, keyed with your secret:
const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(`${req.headers['x-ownlate-timestamp']}.${rawBody}`) .digest('hex')Compare it against X-Ownlate-Signature with a constant-time comparison, and reject a timestamp that is too old to be plausible.
Events
Section titled “Events”Notification and webhook integrations react to these:
| Event | When |
|---|---|
translation.submitted | A translation was sent for review |
translation.reviewed | A reviewer marked one reviewed |
translation.approved | A translation was approved |
translation.rejected | A translation was rejected |
segment.created | A new segment was added to a project |
By default an integration receives all of them. Set an event filter on it to narrow that down — a Slack channel that only wants approvals, a webhook that only cares about new keys.