Skip to content

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.

CategoryProviders
Version controlGitHub, GitLab
Machine translationOpenAI, Anthropic, Mistral, DeepL, Google, Azure — see AI translation
NotificationsSlack, Microsoft Teams, Discord, Mattermost, Matrix, Telegram
WebhooksGeneric webhook

Integrations live on the project’s Integrations tab, and workspace-wide ones under Workspace → Integrations.

ActionWhat it does
Test before savingChecks the credentials you have typed without storing them
TestChecks a saved integration
PauseKeeps the configuration but stops it acting
ActivateTurns a paused one back on
Sync logsThe 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.

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}.json

For 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.

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.

Six chat destinations, each formatted in that platform’s own style — a Block Kit message on Slack, a MessageCard on Teams, and so on.

ProviderWhat it needs
SlackIncoming webhook URL
Microsoft TeamsConnector URL
DiscordWebhook URL
MattermostWebhook URL
TelegramBot token, chat ID
MatrixHomeserver 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.

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:

HeaderContents
X-Ownlate-EventThe event name
X-Ownlate-TimestampMilliseconds since the epoch, the same value as in the body
X-Ownlate-Signaturesha256=…, present only when a secret is set

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.

Notification and webhook integrations react to these:

EventWhen
translation.submittedA translation was sent for review
translation.reviewedA reviewer marked one reviewed
translation.approvedA translation was approved
translation.rejectedA translation was rejected
segment.createdA 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.