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 & How Weblate Receives Events

Weblate exposes a generic /hooks/update/ HTTP endpoint that triggers a VCS pull for all components linked to the matching repository URL. When your SCM fires a push event, a CI step or the SCM’s native webhook delivers the repository URL to this endpoint; Weblate then pulls the updated source strings and queues any configured auto-translation jobs.

Core Data Flow: SCM push eventWebhook delivery (POST /hooks/update/)Weblate VCS pullComponent scanAuto-translation queueCommit translated strings back to VCSPipeline notification

Minimal payload (Weblate generic hook):

{
  "repository": "https://github.com/org/repo.git"
}

Weblate matches the repository URL to registered components. The optional branch field filters which components update. Unlike SCM-native webhooks (GitHub, GitLab), the generic endpoint does not parse a files_changed list — component file masks are configured in the Weblate admin UI, not in the payload.

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 }}"}
          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. 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" \
        '{repository: $repo, branch: $branch}')

      curl -f -X POST \
        -H "Content-Type: application/json" \
        -H "X-Gitlab-Event: Push Hook" \
        -d "$PAYLOAD" \
        "https://weblate.example.com/hooks/update/"
  retry: 2
  timeout: 5m

Custom 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 tag normalization.

# 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)
    # Normalize locale codes in the repository path if present
    if "component" in data:
        data["component"] = normalize_locale(data["component"])
    data["idempotency_key"] = f"{data['repository']}-{data.get('branch', '')}"

    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"}

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. A 400 Bad Request indicates the repository URL does not match any registered component.
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 ‘{“repository”:“https://github.com/org/repo.git”}’
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 is enabled with Fuzzy match threshold ≥ 90%. Ensure the 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).