Skip to main content

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

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.
zavu fn triggers add --events message.inbound --senders snd_abc
When a message.inbound event fires for sender snd_abc, the function runs.
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.

Event types

EventWhen it firesTypical use
message.queuedOutbound message accepted for sendingTrack outbound throughput
message.sentProvider accepted the outboundUpdate CRM with provider IDs
message.deliveredRecipient device received itConversion tracking
message.readRecipient opened it (WhatsApp only)Engagement metrics
message.failedOutbound permanently failedRetry queues, fallback channel
message.inboundCustomer messaged usCustom bots, escalation logic
message.unsupportedInbound message type we don’t handleLog + reply with a fallback
broadcast.status_changedBroadcast state movedPipeline orchestration
conversation.newFirst message ever from a contactWelcome flows, onboarding
template.status_changedWhatsApp template approved/rejectedCI deploys, alerts
invitation.status_changedPartner invitation movedOnboarding follow-up
Run zavu fn triggers events for the always-current list.

Adding triggers

Single event, single sender

zavu fn triggers add \
  --events message.inbound \
  --senders jn76vnxet8g5nq661by3v06y1581bmmn

Single event, all senders in the project

zavu fn triggers add --events message.inbound --senders any

Multiple events × multiple senders (cartesian)

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

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:
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 — same JSON, same fields. The difference: no HTTP receiver needed, no signature verification, no retries to manage. The dispatcher invokes your Lambda directly with the event in the payload.

Pausing without removing

Toggle a trigger off without deleting:
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

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:
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 — 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:
TraitNative triggersSender webhooks
Setupzavu fn triggers addConfigure URL + signing secret per sender
AuthAWS IAM (signed Lambda invoke)HMAC SHA-256 over the body
Latency~50ms (direct invoke)Internet round trip
RetriesLambda native retries (2x by default)You implement
CostOne Lambda invocationOne Lambda + one HTTP request out

Common patterns

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.
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,
  })
})
zavu fn triggers add --events broadcast.status_changed --senders any
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?",
  })
})

API equivalence

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