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

# Triggers

> Bind your function to Zavu events — message.inbound, broadcast.completed, and more.

## Triggers

A trigger says "fire this function when this event happens on this sender."
Triggers are how you build event-driven automation **without** an AI agent —
just raw events, your code reacts.

```sh theme={null}
zavu fn triggers add --events message.inbound --senders snd_abc
```

When a `message.inbound` event fires for sender `snd_abc`, the function
runs.

<Tip>
  If you only want a chatbot, you usually don't need triggers — `defineAgent`
  already binds the agent to its sender. Use triggers when you want **raw
  event delivery**: custom logic before/after the agent, broadcasts, system
  events, or stand-alone automation without an LLM.
</Tip>

## Event types

| Event                       | When it fires                         | Typical use                    |
| --------------------------- | ------------------------------------- | ------------------------------ |
| `message.queued`            | Outbound message accepted for sending | Track outbound throughput      |
| `message.sent`              | Provider accepted the outbound        | Update CRM with provider IDs   |
| `message.delivered`         | Recipient device received it          | Conversion tracking            |
| `message.read`              | Recipient opened it (WhatsApp only)   | Engagement metrics             |
| `message.failed`            | Outbound permanently failed           | Retry queues, fallback channel |
| `message.inbound`           | Customer messaged us                  | Custom bots, escalation logic  |
| `message.unsupported`       | Inbound message type we don't handle  | Log + reply with a fallback    |
| `broadcast.status_changed`  | Broadcast state moved                 | Pipeline orchestration         |
| `conversation.new`          | First message ever from a contact     | Welcome flows, onboarding      |
| `template.status_changed`   | WhatsApp template approved/rejected   | CI deploys, alerts             |
| `invitation.status_changed` | Partner invitation moved              | Onboarding follow-up           |

Run `zavu fn triggers events` for the always-current list.

## Adding triggers

### Single event, single sender

```sh theme={null}
zavu fn triggers add \
  --events message.inbound \
  --senders jn76vnxet8g5nq661by3v06y1581bmmn
```

### Single event, all senders in the project

```sh theme={null}
zavu fn triggers add --events message.inbound --senders any
```

### Multiple events × multiple senders (cartesian)

```sh theme={null}
zavu fn triggers add \
  --events message.delivered,message.read,message.failed \
  --senders snd_abc,snd_xyz
# → 6 triggers (3 events × 2 senders)
```

### Mix specific + any

```sh theme={null}
zavu fn triggers add \
  --events message.inbound \
  --senders snd_abc,any
# → 2 triggers: one for snd_abc, one for "any sender"
```

Duplicates are deduped silently.

## Handling events in code

When a trigger fires, your function's default handler (`defineFunction`)
receives the event:

```ts theme={null}
import { defineFunction } from "@zavu/functions"

export default defineFunction(async (event, ctx) => {
  // event.type is "message.inbound", "broadcast.status_changed", etc.
  // event.data has the payload (shape depends on event)
  // event.senderId is set for sender-scoped events
  // event.projectId, event.timestamp always present

  switch (event.type) {
    case "message.inbound":
      await handleInbound(event.data)
      break
    case "broadcast.status_changed":
      await handleBroadcastStatus(event.data)
      break
  }
})
```

The event shape mirrors the [webhook payload](/guides/receiving-messages/events) —
same JSON, same fields. The difference: no HTTP receiver needed, no signature
verification, no retries to manage. The dispatcher invokes your function
directly with the event in the payload.

## Pausing without removing

Toggle a trigger off without deleting:

```sh theme={null}
zavu fn triggers list
# id            event             sender    active
# tr_abc        message.inbound   snd_xyz   yes

zavu fn triggers toggle tr_abc --off
# now active: no — function won't fire on this event

zavu fn triggers toggle tr_abc
# re-enables
```

Useful for temporary disabling without losing the trigger configuration.

## Removing

```sh theme={null}
zavu fn triggers rm tr_abc
```

## When triggers + defineAgent coexist

If your function has BOTH:

* `defineAgent({...})` running on a sender, AND
* An explicit trigger for `message.inbound` on the same sender

…then **two things happen** for every inbound message:

1. The agent processes the message and replies via tools.
2. Your `defineFunction` default handler also runs (the trigger fires it).

This is by design — you might want raw event access (for logging, custom
analytics, escalation logic) on top of the agent. Just make sure your raw
handler doesn't *also* send a reply or you'll double-message the customer.

To avoid duplicate processing, gate your raw handler:

```ts theme={null}
export default defineFunction(async (event, ctx) => {
  if (event.type !== "message.inbound") return

  // Don't send a reply — the agent does that. Just observe.
  await analytics.track("inbound", {
    contactPhone: event.data.from,
    text: event.data.text,
  })
})
```

## Native vs HTTP webhooks

Triggers are the **native** way to receive events. The old path —
[webhooks on senders](/guides/receiving-messages/webhooks) — still works and
is the right choice when your event receiver lives outside Zavu (an n8n flow,
a Vercel function, an internal server). Inside Zavu Functions, prefer triggers:

| Trait   | Native triggers                 | Sender webhooks                                     |
| ------- | ------------------------------- | --------------------------------------------------- |
| Setup   | `zavu fn triggers add`          | Configure URL + signing secret per sender           |
| Auth    | Zavu-internal signed invocation | HMAC SHA-256 over the body                          |
| Latency | \~50ms (direct invoke)          | Internet round trip                                 |
| Retries | Managed retries (2x by default) | You implement                                       |
| Cost    | One function invocation         | One function invocation + one outbound HTTP request |

## Common patterns

<AccordionGroup>
  <Accordion title="Escalate to human on certain keywords">
    ```ts theme={null}
    export default defineFunction(async (event) => {
      if (event.type !== "message.inbound") return
      const text = (event.data.text ?? "").toLowerCase()
      if (/agente|human|hablar con alguien|complaint/.test(text)) {
        await slack.send(
          `Customer ${event.data.from} wants a human:\n> ${event.data.text}`
        )
      }
    })
    ```

    Add a trigger on `message.inbound` and the agent on the same sender. Both
    run; the LLM responds, your code pages the team.
  </Accordion>

  <Accordion title="Track broadcast completion">
    ```ts theme={null}
    export default defineFunction(async (event) => {
      if (event.type !== "broadcast.status_changed") return
      if (event.data.status !== "completed") return
      await analytics.track("broadcast_done", {
        broadcastId: event.data.id,
        deliveredCount: event.data.deliveredCount,
      })
    })
    ```

    ```sh theme={null}
    zavu fn triggers add --events broadcast.status_changed --senders any
    ```
  </Accordion>

  <Accordion title="Welcome new contacts">
    ```ts theme={null}
    export default defineFunction(async (event, ctx) => {
      if (event.type !== "conversation.new") return
      const zavu = new Zavudev({ apiKey: process.env.ZAVU_API_KEY! })
      await zavu.messages.send({
        to: event.data.contactPhone,
        channel: event.data.channel,
        text: "¡Bienvenido! Soy Bella, en qué te puedo ayudar?",
      })
    })
    ```
  </Accordion>
</AccordionGroup>

## API equivalence

```sh theme={null}
# List
curl https://api.zavu.dev/v1/functions/$FN_ID/triggers \
  -H "Authorization: Bearer $KEY"

# Create
curl -X POST https://api.zavu.dev/v1/functions/$FN_ID/triggers \
  -H "Authorization: Bearer $KEY" \
  -d '{"eventTypes":["message.inbound"],"senderIds":["snd_abc",null]}'
# null in senderIds means "any sender"

# Toggle
curl -X PATCH https://api.zavu.dev/v1/functions/triggers/$TRIGGER_ID \
  -H "Authorization: Bearer $KEY" \
  -d '{"active":false}'

# Delete
curl -X DELETE https://api.zavu.dev/v1/functions/triggers/$TRIGGER_ID \
  -H "Authorization: Bearer $KEY"
```
