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.
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.
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.
zavu fn triggers list# id event sender active# tr_abc message.inbound snd_xyz yeszavu fn triggers toggle tr_abc --off# now active: no — function won't fire on this eventzavu fn triggers toggle tr_abc# re-enables
Useful for temporary disabling without losing the trigger configuration.
An explicit trigger for message.inbound on the same sender
…then two things happen for every inbound message:
The agent processes the message and replies via tools.
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, })})
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: