"@zapier/zapier-sdk/experimental" to use these
methods. For the CLI, run commands via the zapier-sdk-experimental binary,
pass --experimental to zapier-sdk, or set ZAPIER_EXPERIMENTAL=true in
the environment. Methods and behavior may change between versions.(app, action, connection, inputs) combination. Zapier handles the subscription and buffers incoming events. Your code leases messages from the inbox and processes them at its own pace.
CLI and SDK, complementary by designThe CLI is the fastest way to explore: find the right action key, inspect what inputs a subscription needs, create a trigger inbox, and interactively test it before writing any code. For scripting and one-off runs, the CLI alone may be enough.
When you need a production-grade consumer (a typed handler, a long-running process with graceful shutdown, an AI agent reacting to events in code), reach for the TypeScript SDK. Everything you explored with the CLI transfers directly.
Prerequisites
Install and authenticate the CLI as shown in the quickstart. Experimental entry point — import from@zapier/zapier-sdk/experimental to access Triggers. The factory is the same createZapierSdk you already know:
Key Concepts
Trigger — emits events when something happens in a connected app. Every integration defines its own triggers with their own action keys. UselistTriggers to discover what a specific app supports.
Trigger Inbox — a server-side subscription you register for a specific (app, action, connection, inputs) combination. Zapier connects to the app and buffers incoming events for you.
Inboxes move through a small lifecycle:
initializing(Zapier is setting up the subscription)active(collecting events)paused(collection stopped, buffered messages preserved)deleting(scheduled for removal)initialization_failure(setup failed; checkpaused_reason)
id, created_at, status, a payload with the raw event data from the app, and message_attributes with lease_count (how many times it’s been leased), error_message (last processing error), and possible_duplicate_data (flag if deduplication wasn’t possible).
Lease and ack — messages are leased (hidden from other consumers) while you process them, then acked (permanently removed) when processing succeeds. If your handler throws, the message returns to the available pool when the lease expires. drainTriggerInbox and watchTriggerInbox manage leasing, acking, and retries for you automatically.
Walkthrough: Subscribe to Slack messages
Step 1: Discover triggers for an app
listTriggers returns all triggers available for an app. The key field is what you pass as action when creating an inbox.
TypeScript SDK
CLI
Step 2: Inspect a trigger’s required inputs
UselistTriggerInputFields to see what inputs the subscription requires. These define the scope of events that get buffered — for example, which Slack channel to watch.
TypeScript SDK
CLI
key values are what you’ll pass as inputs when creating the inbox.
Step 3: Ensure an inbox
ensureTriggerInbox is generally recommended for production use: it’s idempotent on name, so restarting your process or re-deploying doesn’t create duplicate subscriptions. Use createTriggerInbox only when you want a throwaway inbox with an auto-generated name.
TypeScript SDK
CLI
Step 4: Drain messages once
drainTriggerInbox leases all currently-available messages, calls your onMessage handler for each, and resolves when the inbox is empty (or maxMessages is reached). Right for one-shot processing: a cron job, a script, or a webhook handler.
TypeScript SDK
message object including message.payload (the raw event data). Return from the handler to ack the message; throw to trigger release or retry behavior.
Key options:
--max-messages/maxMessagescaps how many to drain.--concurrency/concurrencyruns multiple handlers in parallel.--release-on-error/releaseOnErrorreleases failed messages when the drain finishes, instead of waiting for the full lease timeout.
CLI
--exec-shell instead when you need shell features like pipes, redirects, or env expansion (e.g. --exec-shell "./handler | jq .payload").
Refer to the Triggers CLI reference for the full list of options.
Step 5: Watch continuously
watchTriggerInbox runs indefinitely, consuming messages as they arrive. It drains all currently-available messages, then holds open a Server-Sent Events (SSE) connection and re-drains the moment Zapier notifies it of new arrivals, so wake-ups are near-real-time. A periodic safety drain runs as a backstop in case a notification is missed. Use it for long-running services (e.g. a server-side consumer/worker, or a CLI tool that runs continuously).
TypeScript SDK
AbortController gives you clean shutdown: aborting closes the SSE connection, cancels in-flight HTTP requests, releases unprocessed messages back to the inbox, and resolves the call rather than rejecting it. Hook it to SIGTERM and SIGINT so your process shuts down gracefully.
Run under a process supervisor — for production, keep the watcher alive across crashes and reboots with a supervisor like pm2, systemd (Linux), launchd (macOS), or brew services (macOS, dev). Configure the supervisor to send SIGTERM on stop so the shutdown handling above runs cleanly.
CLI
--max-drain-interval-seconds sets the safety-drain interval, the longest the watcher will go without draining if no SSE notification arrives (default: 300s). If real-time wake-ups pause, the watcher logs a warning to stderr and falls back to that safety drain (run with --debug for transient reconnect notices); stdout, including --json NDJSON, stays clean for piping. --exec and --exec-shell work the same as in drain-trigger-inbox.
Refer to the Triggers CLI reference for the full list of options.
How the watcher wakes up
After the initial drain,watchTriggerInbox keeps a single SSE connection open to Zapier and re-drains the inbox whenever a notification arrives, so new messages are picked up in near-real-time rather than on a fixed interval. Three things can trigger a drain:
- An SSE notification — the normal path; fires within moments of a new message landing in the inbox.
- The safety drain — a periodic backstop (every
maxDrainIntervalSeconds, default 300s) that runs regardless of SSE state, so the inbox continues to be processed even if a notification is missed or the connection drops undetected. - Connection (re)open — the watcher drains once whenever the SSE connection is established, to catch anything that arrived while it was offline.
--debug on the CLI, or debug: true in the SDK options). This output is informational and does not require action.
Under the hood: lease, ack, release
drainTriggerInbox and watchTriggerInbox compose three lower-level operations that are worth understanding when debugging unexpected behavior:
Lease — leaseTriggerInboxMessages reserves a batch of messages for a configurable lease window (see the Triggers API reference for defaults and limits). While leased, messages are hidden from other consumers. message_attributes.lease_count increments each time a message is leased.
Once lease_count reaches 5, the message transitions to quarantined. Quarantined messages are no longer returned via lease, drain, or watch, but they remain visible in listTriggerInboxMessages output. Quarantine is a fail-safe for the experimental period: if a bug causes processing to fail at scale, we can identify the cause, fix it, and clear leases server-side. There is no end-user mechanism to recover a quarantined message today, so design your handlers to drop known-bad messages before they hit the threshold (see Ack-to-drop poison messages below).
Ack — ackTriggerInboxMessages permanently removes messages from the inbox. The drain/watch API acks automatically when your handler returns without throwing.
Release — releaseTriggerInboxMessages returns messages to the available pool before the lease expires. Use it when you want to give up on a batch early without waiting for timeout.
Ack-to-drop poison messages — if a message keeps failing for reasons your handler can detect (malformed payload, missing required field, repeated upstream error), check message.message_attributes.lease_count and explicitly ack it to remove it from the inbox before it quarantines. Pairs well with continueOnError: true, so one bad message doesn’t reject the whole drain.
Control-flow signals from onMessage give fine-grained control without rejecting the entire drain:
- Throw
ZapierReleaseTriggerMessageSignalto release just this one message and continue draining. - Throw
ZapierAbortDrainSignalto finish the current batch, then resolve the drain cleanly.
releaseOnError: true — by default, when onMessage throws, the message stays leased until the timeout. Pass releaseOnError: true to release it when the drain finishes instead, so it becomes available for re-processing without waiting out the full lease.
continueOnError: true — by default, the first handler error rejects the entire drain (fail-fast). Pass continueOnError: true to keep draining: handler errors are routed to an optional onError(error, message) observer, and the message is then released-or-left per releaseOnError. SDK-level errors (lease/ack/release HTTP failures) still reject regardless. On the CLI, the equivalent flag is --continue-on-error.
leaseTriggerInboxMessages, ackTriggerInboxMessages, releaseTriggerInboxMessages), see the Triggers API reference.
Inbox management
List inboxes — find inboxes by status or name:TypeScript SDK
CLI
Pause and resume — pausing stops event collection without discarding buffered messages. Resuming restarts it.
TypeScript SDK
CLI
Delete — marks the inbox for deletion and cancels the server-side subscription:
Update — currently only
notificationUrl can be updated on an existing inbox:
Webhook delivery: notificationUrl
watchTriggerInbox keeps a persistent SSE connection open and reacts to Zapier’s notifications, which suits a long-running process. If you’d rather not hold a connection open (for example, a serverless or stateless consumer) and you operate a server with a public endpoint, pass notificationUrl when creating the inbox instead. Zapier POSTs to that URL whenever new messages arrive, so your endpoint can call drainTriggerInbox on-demand.
TypeScript SDK
CLI
drainTriggerInbox to process the buffered messages. This is the right pattern for consumers that can’t hold a persistent connection open, such as serverless functions.
Next Steps
- Triggers API reference — full parameter docs for every trigger method
- CLI reference — all
zapier-sdk-experimentalcommands - Deploy with client credentials — run your trigger consumer in production without a browser login
- Give feedback — your input shapes what we build next