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

# Payload templates

> Reshape the webhook body with Handlebars. Variable reference and worked examples for common receivers.

When the canonical payload doesn't fit your receiver, paste a [Handlebars](https://handlebarsjs.com) template into the channel form's **Payload template** field. Tallwatch renders it against the canonical variables and POSTs the result.

Tallwatch uses stock Handlebars with HTML escaping turned off. You get the standard block helpers (`#if`, `#unless`, `#each`, `#with`) and nothing custom, so there's no `join`, `upper`, or `eq`. The examples below stick to what's actually available.

<Tip>
  Test every template with **Send test alert** before binding the channel to real monitors. The test shows you exactly what your receiver gets.
</Tip>

## Variables

The template runs against the same object documented on the [Payload page](/webhooks/payload). Use dot-notation for nested fields.

| Variable                       | Type              | Example                       |
| ------------------------------ | ----------------- | ----------------------------- |
| `{{event}}`                    | string            | `incident.opened`             |
| `{{occurred_at}}`              | string (ISO 8601) | `2026-06-02T14:23:01.829Z`    |
| `{{org.name}}`                 | string            | `Acme Corp`                   |
| `{{org.slug}}`                 | string            | `acme`                        |
| `{{monitor.name}}`             | string            | `API healthcheck`             |
| `{{monitor.type}}`             | string            | `http`                        |
| `{{monitor.url}}`              | string            | `https://api.acme.com/health` |
| `{{incident.id}}`              | string            | `4720133`                     |
| `{{incident.status}}`          | string            | `open` / `resolved`           |
| `{{incident.opened_at}}`       | string (ISO 8601) | `2026-06-02T14:21:30.500Z`    |
| `{{incident.resolved_at}}`     | string \| null    | `null` on open events         |
| `{{incident.duration_sec}}`    | number \| null    | `1037`                        |
| `{{incident.failing_regions}}` | string\[]         | `["fra1", "iad1", "sgp1"]`    |
| `{{check.last_response_ms}}`   | number \| null    | `8123`                        |
| `{{check.last_error_class}}`   | string \| null    | `timeout`                     |

## Escaping

Templates render with HTML escaping off, so `{{monitor.name}}` outputs the raw value. You don't need triple-stash.

The trade-off is that nothing JSON-escapes either. A value containing a `"` will break a templated JSON body, and there's no built-in helper to escape it. Keep templated string fields to values you control, like a monitor name you named yourself. If a body could contain arbitrary user text with quotes, send the canonical payload instead, which is always valid JSON.

## Joining an array

There's no `join` helper, so iterate with `#each` and suppress the trailing separator with `@last`:

```handlebars theme={null}
{{#each incident.failing_regions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
```

That renders `fra1, iad1, sgp1`.

## Worked examples

### Slack-compatible body

For a Slack incoming webhook with more control than the native Slack channel gives you:

```handlebars Slack template theme={null}
{
  "text": "{{monitor.name}} is {{incident.status}}",
  "attachments": [
    {
      "color": "{{#if incident.resolved_at}}good{{else}}danger{{/if}}",
      "fields": [
        {
          "title": "Regions",
          "value": "{{#each incident.failing_regions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}",
          "short": true
        },
        {
          "title": "Duration",
          "value": "{{#if incident.duration_sec}}{{incident.duration_sec}}s{{else}}ongoing{{/if}}",
          "short": true
        }
      ]
    }
  ]
}
```

### Datadog event

Push incidents into Datadog's [event stream](https://docs.datadoghq.com/api/latest/events/):

```handlebars Datadog template theme={null}
{
  "title": "Tallwatch: {{monitor.name}} is {{incident.status}}",
  "text": "Failing regions: {{#each incident.failing_regions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}",
  "alert_type": "{{#if incident.resolved_at}}success{{else}}error{{/if}}",
  "tags": ["service:{{monitor.name}}", "tallwatch:{{event}}"]
}
```

### Minimal internal alert

When your ticketing system just needs a subject and a link:

```handlebars Minimal template theme={null}
{
  "subject": "[Tallwatch] {{monitor.name}}: {{event}}",
  "url": "https://tallwatch.com/incidents/{{incident.id}}"
}
```

### Status page publisher

Re-broadcast to a status-page tool that accepts a webhook:

```handlebars Statuspage template theme={null}
{
  "incident": {
    "name": "{{monitor.name}} degraded",
    "status": "{{#if incident.resolved_at}}resolved{{else}}investigating{{/if}}",
    "body": "Tallwatch detected this in {{#each incident.failing_regions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}. We're investigating.",
    "impact_override": "minor"
  }
}
```

## Branch by event type

There's no string-equality helper, so don't compare `event` directly. Branch on the field that distinguishes the events instead. `incident.resolved_at` is set only on resolve:

```handlebars theme={null}
{
  "subject": "{{#if incident.resolved_at}}[RESOLVED]{{else}}[OPEN]{{/if}} {{monitor.name}}"
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="My receiver got the default payload, not my template">
    A template that fails to compile or render doesn't block the alert. Tallwatch logs the error, falls back to the canonical JSON payload, and the dispatch is marked `sent`. So a broken template shows up as "my receiver got the default shape." Re-test with **Send test alert** and fix the template.
  </Accordion>

  <Accordion title="My receiver returns 400 'invalid JSON'">
    A value in a string field contained a `"`, `\`, or control character and broke the JSON, since templates don't escape. Keep arbitrary user text out of templated JSON string fields, or fall back to the canonical payload.
  </Accordion>

  <Accordion title="A field renders empty">
    Handlebars renders a missing value as an empty string, not `undefined`. If you literally see the word `undefined` in the output, something on your receiver side is stringifying it. Check what Tallwatch sent with a test alert first.
  </Accordion>

  <Accordion title="`failing_regions` is empty on resolve">
    Expected: on resolve, every region agrees the monitor is up, so the array is empty. Guard region-dependent parts:

    ```handlebars theme={null}
    {{#if incident.failing_regions.length}}
      Regions affected: {{#each incident.failing_regions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
    {{else}}
      All regions recovered.
    {{/if}}
    ```
  </Accordion>
</AccordionGroup>

## When not to template

If your receiver accepts the canonical payload, leave the field blank:

* One less thing to update when the payload gains a field.
* The canonical body is always valid JSON; a template can produce broken JSON.
* A predictable body makes the dispatch history easier to read.

Templates are an escape hatch for receivers that demand a specific shape, not a default to reach for on every channel.
