> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usekairo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook triggers

> Fire a Kairo automation from any external service via a single secret URL.

A webhook automation gives you one URL. Anyone or anything that can `POST` to it triggers the automation, and the result lands in your Telegram chat — same delivery as a scheduled run.

This is the contract third-party services see: GitHub Actions, Linear, Zapier, monitoring tools, iOS Shortcuts, your own scripts. If you can `curl`, you can fire a Kairo automation.

## Creating a webhook automation

Webhook automations are created from the [web app](https://usekairo.ai), not from chat:

1. Go to **Automations** → **New automation**.
2. Switch the trigger type from **Schedule** to **Webhook**.
3. Give it a name and write the instruction Kairo should run when the webhook fires (e.g. *"Send me a Telegram message summarizing the payload"*).
4. Save. A modal shows your webhook URL **once** — copy it now and paste it into the service that will call it.

You can reveal the URL again later from the automations list (eye icon next to the row), and rotate it if it leaks.

## URL shape

```http theme={null}
POST https://api.usekairo.ai/webhooks/automations/{token}
```

The token is opaque (`whk_` prefix + 32 random bytes, URL-safe). It's the only credential — there's no separate API key, no signature header.

## Sending requests

Send `POST` with `Content-Type: application/json` (or `application/x-www-form-urlencoded`) and any JSON body. The body is delivered to Kairo's planner as context, so include whatever fields you want Kairo to be able to reference.

```bash theme={null}
curl -X POST https://api.usekairo.ai/webhooks/automations/whk_xxxx \
  -H "Content-Type: application/json" \
  -d '{"event":"deploy_failed","service":"api","commit":"abc123"}'
```

### Accepted

| Aspect        | Value                                                   |
| ------------- | ------------------------------------------------------- |
| Methods       | `POST` only                                             |
| Content types | `application/json`, `application/x-www-form-urlencoded` |
| Max body size | 256 KB                                                  |
| Empty body    | OK — treated as `{}`                                    |

## Responses

| Status                       | Meaning                                                                                                                                          |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `202 Accepted`               | Token resolved, run accepted. Body: `{ "ok": true, "run_id": "<uuid>" }`. Kairo runs the automation asynchronously and replies to your Telegram. |
| `404 Not Found`              | Token doesn't match any automation (deleted, rotated, or wrong).                                                                                 |
| `410 Gone`                   | Token resolves, but the automation is paused. Re-enable it from the web app.                                                                     |
| `413 Payload Too Large`      | Body over 256 KB.                                                                                                                                |
| `415 Unsupported Media Type` | Content type isn't JSON or form-encoded.                                                                                                         |
| `429 Too Many Requests`      | Rate limit hit. Honor the `Retry-After` header.                                                                                                  |

The 202 body is the only successful shape, and it's replayed verbatim on idempotency hits.

## Idempotency

Set the `Idempotency-Key` header to safely retry the same logical event:

```http theme={null}
POST /webhooks/automations/whk_xxxx
Idempotency-Key: deploy-202604011423-abc123
Content-Type: application/json
```

* Within **24 hours**, repeating the same `(automation, key)` pair replays the original `202` response and **does not** re-fire the run.
* Without the header, every request fires — useful for cron callers that intentionally re-send the same payload on a schedule.

The 24 h window matches industry-standard practice (Stripe, GitHub).

## Rate limits

Each automation is capped at **60 requests per minute**. Excess requests get `429` with a `Retry-After` header indicating seconds until the next slot.

## What gets stored

Every accepted webhook call creates a row in your automation's run history (visible in the web app). Kairo stores:

* The full payload, **truncated to 32 KB** if larger (the truncated marker is `{ "_truncated": true, "_preview": "<first 32KB>" }`).
* The trigger time.
* The reply Kairo sent to Telegram.
* Run status (`success` / `error`) and duration.

Tokens never appear in logs. Path tokens are redacted to `[redacted]` in all log output.

## Failure handling

* **Pre-acceptance failures** (404, 410, 413, 415, 429) don't count against the automation. Only the HTTP caller sees them.
* **Post-acceptance failures** (something went wrong while Kairo was running the automation) are recorded and counted.
* After **5 consecutive run failures** the automation auto-pauses. You'll get a Telegram message letting you know, and the webhook URL starts returning `410 Gone` until you re-enable it.

## Rotating the token

If your webhook URL leaks, rotate it:

1. Web app → **Automations** → the row → **⋯** → **Rotate token**.
2. Confirm. The old URL stops working immediately — there's no grace period.
3. The new URL is shown in a one-time-reveal modal. Update it everywhere it's used.

## What's not supported (yet)

* Sender signing (HMAC). Kairo mints the URL; sender authentication isn't the threat model.
* Per-source secrets or IP allow-lists.
* Custom payload schemas — Kairo's planner reads your JSON freeform.
* Multipart, `text/plain`, or XML payloads.

## End-to-end example: GitHub Actions

```yaml theme={null}
- name: Notify Kairo on deploy
  run: |
    curl -X POST https://api.usekairo.ai/webhooks/automations/${{ secrets.KAIRO_DEPLOY_HOOK }} \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: deploy-${{ github.sha }}-${{ github.run_attempt }}" \
      -d '{
        "repo": "${{ github.repository }}",
        "sha": "${{ github.sha }}",
        "actor": "${{ github.actor }}",
        "status": "${{ job.status }}"
      }'
```

With an instruction like *"Tell me which repo deployed and the short SHA — celebrate if status is success, alert if failure"*, you'll get a Telegram message every time the workflow runs.
