Configuring Weblate Webhooks for Auto-Translation

Introduction to Event-Driven Localization

Modern i18n pipelines require seamless synchronization between code deployments and translation updates. Manual string extraction and periodic batch updates create bottlenecks in agile release cycles, leading to stale localization files, inconsistent UI copy across environments, and delayed feature rollouts for international markets. Teams relying on scheduled syncs frequently encounter merge conflicts and context drift when source strings change between translation cycles. Implementing a robust Weblate Self-Hosted Setup eliminates manual intervention by bridging your version control system with Weblate’s translation engine, reducing context switch overhead for engineering and localization teams.

Webhook Architecture & Payload Structure

The webhook listener expects a standardized JSON payload containing repository metadata, branch references, and component identifiers. Proper payload formatting ensures the translation queue processes only relevant files, reducing unnecessary machine translation (MT) API calls and preventing translation memory pollution.

Core Data Flow: SCM EventWebhook PayloadWeblate Hook Receiver (/hooks/update/)Component ScanAuto-Translation ExecutionCommit Back to VCSPipeline Notification

Required Payload Schema:

{
 "repository": "https://github.com/org/repo.git",
 "branch": "main",
 "component": "frontend/locales",
 "operation": "push",
 "files_changed": ["locales/en.json", "locales/fr.json"]
}

Weblate’s /hooks/update/ endpoint consumes this schema, maps it to the configured VCS component, and queues the auto-translation engine. Ensure the payload strictly adheres to this structure to bypass schema validation rejections.

CI/CD Pipeline Integration

Embedding webhook triggers into your Translation Workflows & CI/CD Pipeline Sync strategy guarantees that localization keeps pace with feature development. Configure pipeline stages to dispatch hooks only after successful linting, unit testing, and build validation. Triggering auto-translation on broken or incomplete source strings corrupts translation memory and forces manual cleanup.

Pipeline Guardrails:

  1. Run i18n-extract or formatjs compile as a pre-hook validation step.
  2. Filter webhook dispatches to paths-ignore: ['*.md', 'docs/**', 'tests/**'].
  3. Enforce branch protection: only main, release/*, or l10n/* branches may trigger auto-translation.

Framework-Specific Implementation

GitHub Actions

Use push or workflow_dispatch triggers to POST to the Weblate webhook URL. Implement HMAC-SHA256 signature verification and exponential backoff for concurrent pushes.

name: Trigger Weblate Auto-Translation
on:
 push:
 branches: [main, release/*]
 paths-ignore:
 - '**/*.md'
 - 'docs/**'

jobs:
 weblate-sync:
 runs-on: ubuntu-latest
 steps:
 - name: Generate HMAC Signature
 id: hmac
 run: |
 PAYLOAD=$(cat <<EOF
 {"repository":"${{ github.repositoryUrl }}","branch":"${{ github.ref_name }}","component":"frontend/locales","operation":"push"}
 EOF
 )
 SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.WEBHOOK_SECRET }}" | awk '{print $2}')
 echo "signature=$SIGNATURE" >> $GITHUB_OUTPUT
 echo "$PAYLOAD" > /tmp/payload.json

 - name: Dispatch to Weblate
 uses: fjogeleit/http-request-action@v1
 with:
 url: 'https://weblate.example.com/hooks/update/'
 method: 'POST'
 customHeaders: '{"X-Hub-Signature-256": "sha256=${{ steps.hmac.outputs.signature }}", "Content-Type": "application/json"}'
 data: '@${{ github.workspace }}/payload.json'
 timeout: 30000
 retry: 3
 retryDelay: 1000

GitLab CI

Configure pipeline webhooks with custom JSON payload mapping. Utilize CI_JOB_TOKEN for authenticated API calls when bypassing public webhook exposure. Restrict execution to default branch pipelines.

trigger_weblate:
 stage: deploy
 image: curlimages/curl:latest
 rules:
 - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
 script:
 - |
 PAYLOAD=$(jq -n \
 --arg repo "$CI_REPOSITORY_URL" \
 --arg branch "$CI_COMMIT_BRANCH" \
 --arg comp "frontend/locales" \
 '{repository: $repo, branch: $branch, component: $comp, operation: "push"}')
 
 curl -f -X POST \
 -H "Content-Type: application/json" \
 -H "Authorization: Bearer $CI_JOB_TOKEN" \
 -H "X-Gitlab-Event: Push Hook" \
 -d "$PAYLOAD" \
 "https://weblate.example.com/hooks/update/"
 retry: 2
 timeout: 5m

Custom Node.js/Python Proxy Middleware

Deploy a lightweight proxy to validate signatures, normalize locale codes (e.g., zh-CNzh_Hans), and enforce idempotency. Prevents duplicate translation jobs and handles BCP 47 fallbacks.

# proxy_server.py (FastAPI)
from fastapi import FastAPI, Request, HTTPException, Header
import hashlib, hmac, json, os
from httpx import AsyncClient

app = FastAPI()
WEBLATE_URL = os.getenv("WEBLATE_URL")
SHARED_SECRET = os.getenv("WEBHOOK_SECRET").encode()

def verify_hmac(payload: bytes, signature: str) -> bool:
 expected = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
 return hmac.compare_digest(f"sha256={expected}", signature)

def normalize_locale(tag: str) -> str:
 mapping = {"zh-CN": "zh_Hans", "zh-TW": "zh_Hant", "pt-BR": "pt_BR"}
 return mapping.get(tag, tag.replace("-", "_"))

@app.post("/webhook/forward")
async def forward_to_weblate(request: Request, x_signature: str = Header(None)):
 payload = await request.body()
 if not verify_hmac(payload, x_signature):
 raise HTTPException(status_code=403, detail="Invalid HMAC signature")
 
 data = json.loads(payload)
 data["component"] = normalize_locale(data.get("component", ""))
 data["idempotency_key"] = f"{data['repository']}-{data['branch']}-{data.get('operation')}"
 
 async with AsyncClient() as client:
 resp = await client.post(f"{WEBLATE_URL}/hooks/update/", json=data)
 if resp.status_code != 200:
 raise HTTPException(status_code=resp.status_code, detail=resp.text)
 return {"status": "queued", "weblate_response": resp.json()}

Debugging & Troubleshooting Common Edge Cases

Step Action Expected Outcome & Verification
1 Verify webhook delivery status in SCM dashboard HTTP 200 OK from Weblate /hooks/update/. Check SCM delivery logs for exact payload and response headers.
2 Inspect Weblate audit log for hook processing errors Navigate to Admin → Audit Log. Filter by Hook or Auto-translation. Status must show completed or queued. Look for 400 Bad Request indicating malformed JSON.
3 Validate HMAC signature mismatch Ensure the shared secret matches exactly. Strip trailing newlines/whitespace from environment variables. Verify hash algorithm alignment (sha256 vs sha1). Test locally: `echo -n ‘{“test”:true}’
4 Check auto-translation component settings In Weblate UI, verify: (a) MT services are active and quota-available, (b) Source/Target language mapping matches BCP 47 tags, © Auto translate checkbox is enabled with Fuzzy match threshold ≥ 90%. Ensure component is not locked by concurrent merge operations.

Proactive Logging Strategy: Implement structured JSON logging at the webhook receiver level. Cross-reference request_id across SCM delivery logs, proxy middleware, and Weblate audit trails for precise failure attribution. When MT quota exhaustion occurs, configure fallback to glossary-only matching or queue retry with exponential backoff (2^n * 1000ms).