Webhooks
Webhooks tell your systems the moment something changes in a project, so you don't have to poll the API on a timer. TextSetu sends a signed HTTP request to a URL you choose, every time an event you've subscribed to happens.
Typical uses:
- Rebuild or redeploy when translations are approved.
- Invalidate a cache or CDN when a value changes.
- Kick off a CI job when a branch is merged.
- Sync to another system when keys are added or removed.
Over the API. Webhooks can be managed entirely over the API as well as in the UI, and the payload schemas, headers and signature algorithm are all documented per-endpoint. See the Webhooks endpoints in the API Reference.
Creating a webhook
- Open Project settings → Webhooks and choose New webhook.
- Enter the Endpoint URL. It must be publicly reachable over HTTPS.
- Pick the events you want. Start from a preset — All events, Translation content, Pipeline events — then fine-tune, or tick individual events.
- Leave Include AI-generated changes off unless you want them (see Keeping the volume sane).
- Save. TextSetu shows a signing secret beginning
whsec_.
:::warning Copy the signing secret immediately It is shown once and cannot be retrieved afterwards. If you lose it, rotate the secret to get a new one. :::
Your receiver uses that secret to verify each request genuinely came from TextSetu. Until you verify signatures, treat the endpoint as untrusted — anyone who learns the URL could post to it.
Confirming it works
Use Test on the endpoint row. That queues a ping event, which shows up in
the delivery log within seconds. It's the quickest way to prove the URL is
reachable and your signature check is correct before you depend on real
events.
Events
| Event | Fires when |
|---|---|
| Key created | A translation key is added |
| Key updated | A key's name, description, tags or screenshot changes |
| Key deleted | A key is removed |
| Translation updated | A translation's text or approval status changes, for one language |
| Branch created | A translation branch is created |
| Branch merged | A branch is merged into the main line, with a summary of what changed |
| Import completed | A file import finishes successfully |
| Export completed | A file export finishes and is ready to download |
| Language added / removed | A project language changes |
Subscription is explicit. An endpoint only ever receives the events you tick. There is no "everything" option that silently grows — if TextSetu adds a new event type later, existing endpoints keep receiving exactly what they received before until you opt in. New events can't become surprise traffic.
Branches
Changes made on a branch are not delivered. Branch edits are staged as a diff and are deliberately not part of project activity, so editing a translation or adding a key on a branch fires nothing.
You're notified at the boundaries instead:
- Branch created when the work starts.
- Branch merged when it lands — carrying the number of keys added, keys deleted and values updated.
For a branch-based workflow, treat Branch merged as your signal to re-pull. That is the point at which the branch's changes become live on main.
Keeping the volume sane
Three things stop webhooks becoming a firehose:
- An import fires one event, not one per key. A 5,000-key import is a single Import completed delivery.
- AI-generated changes are excluded by default. A bulk AI translation run can produce thousands of Translation updated events, so the Include AI-generated changes switch is off until you turn it on.
- Unsubscribed events are never queued at all — not sent, not retried, and absent from the log.
The delivery log
Every attempt is recorded beneath the endpoints table for 30 days, whether it succeeded or not. Click a row to see the exact payload sent, the response received, and how many attempts have been made.
| Status | Meaning |
|---|---|
| pending | Queued, not attempted yet |
| success | Delivered — your endpoint returned a 2xx |
| failed | Failed, and a retry is scheduled |
| dead | Gave up — attempts exhausted, or the response was permanently unacceptable |
| skipped | Never sent (endpoint disabled, or the source change no longer exists) |
Filter by endpoint, event type or status, and sort any column. Use Refresh to pull in deliveries that have landed since you opened the page — retries arrive minutes or hours later, and the log doesn't update on its own.
Replaying a delivery
Replay re-sends a delivery's original payload — useful after fixing an outage on your side. The replay is a new delivery with its own webhook ID, so a receiver that de-duplicates on that ID will still process it. The original record is left untouched.
Retries
A failed delivery is retried up to five times over roughly seven and a half hours:
5s → 5min → 30min → 2h → 5h
What gets retried:
- Retried:
408,429, any5xx, plus timeouts and connection or TLS failures. - Not retried: other
4xxresponses (401,403,404,410, …). Those won't fix themselves, and retrying for seven hours wouldn't help — the delivery log tells you exactly what to fix. - Not retried: redirects. TextSetu never follows them, so a
3xxmeans the configured URL is wrong.
If your endpoint returns a Retry-After header on a 429 or 503, TextSetu
honours it.
Auto-disabling
After 10 consecutive failed deliveries an endpoint is switched off automatically and shown as Auto-disabled. That represents roughly ten exhausted retry windows, so it means genuinely dead rather than a brief blip.
Fix your side, then Enable it again — which also clears the failure count, giving the endpoint a full retry budget.
Rotating the signing secret
Choose Rotate secret to issue a new one. The previous secret keeps working for 24 hours, and during that window TextSetu signs each request with both. A receiver that accepts any valid signature can roll over without dropping a single delivery.
Deploy the new secret within those 24 hours. After that, only the new one is sent.
URL requirements
URLs are checked when you save and again on every delivery — DNS can change after an endpoint is created.
- HTTPS is required.
- No credentials in the URL (
https://user:pass@…) and no#fragment. - Must resolve to a public address. Private, loopback, link-local (including cloud metadata addresses) and carrier-NAT ranges are rejected, so a webhook can't be used to reach internal infrastructure.
- Redirects are never followed — point the endpoint at its final URL.
Delivery guarantees
Design your receiver around these three properties:
- At-least-once. A delivery can arrive more than once. De-duplicate on the
webhook-idheader. - Unordered. Two changes made in quick succession may arrive in either order. Don't infer sequence from arrival time.
- Acknowledge fast. Return a 2xx quickly and do the real work asynchronously — TextSetu gives up on an attempt after 10 seconds.
Privacy
Translation updated includes the old and new translation text, sent to the URL you configure. That's the point of the event, but it does mean translation content leaves TextSetu — subscribe deliberately, and only to endpoints you control.
Troubleshooting
| Symptom | Cause |
|---|---|
Every delivery is dead with a 401/403 | Your endpoint is rejecting the request — usually signature verification. Check you're using the base64-decoded secret bytes, not the whsec_… string. |
Deliveries are dead with a 404 | Wrong path, or the route only accepts GET. Webhooks are always POST. |
| The signature never matches | Verify against the raw request body, before JSON parsing. Re-serializing changes the bytes. |
| The endpoint shows Auto-disabled | 10 consecutive failures. Fix the receiver, then Enable. |
| Nothing arrives at all | Check the event is ticked and the endpoint is Enabled — and if the change was made on a branch, see Branches. |
| Nothing arrives from AI translation | Turn on Include AI-generated changes. |
A delivery is skipped | The endpoint was disabled when it came up for sending, or the key it described was deleted before it went out. |
Permissions
Creating, editing and deleting webhooks — and viewing delivery payloads — requires project management access, the same level as project API tokens, since a webhook secret can be used to impersonate TextSetu to your receiver. Listing endpoints and the delivery log needs project read access. See Roles and permissions.