Skip to main content
The model is simple: you create a trigger inbox that subscribes to a specific (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 design

The 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. Import createZapierSdk from @zapier/zapier-sdk to initialize the SDK:
The trigger commands are available on the standard zapier-sdk binary:

Key Concepts

Trigger — emits events when something happens in a connected app. Every integration defines its own triggers with their own action keys. Use listTriggers 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:
  1. initializing (Zapier is setting up the subscription)
  2. active (collecting events)
  3. paused (collection stopped, buffered messages preserved)
  4. deleting (scheduled for removal)
  5. initialization_failure (setup failed; check paused_reason)
Message — an event buffered in the inbox. Each has an 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

Response

Step 2: Inspect a trigger’s required inputs

Use listTriggerInputFields 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

Response
The 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 its key, 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 key.

TypeScript SDK

CLI

Response
The inbox is now active: Zapier is watching for new Slack messages and buffering them.

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

The handler receives the full 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 / maxMessages caps how many to drain.
  • --concurrency / concurrency runs multiple handlers in parallel.
  • --release-on-error / releaseOnError releases failed messages when the drain finishes, instead of waiting for the full lease timeout.
Refer to the Triggers API reference for the full list of options.

CLI

For custom per-message processing, pipe the message JSON from stdin to your own script or command:
Use --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

The 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.
If the connection drops, the watcher reconnects automatically with backoff while the safety drain covers the gap. Connection health is reported on stderr: a warning when real-time wake-ups pause and the watcher falls back to the safety drain, plus transient reconnect notices when debug logging is enabled (--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: LeaseleaseTriggerInboxMessages 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: 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). AckackTriggerInboxMessages permanently removes messages from the inbox. The drain/watch API acks automatically when your handler returns without throwing. ReleasereleaseTriggerInboxMessages 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 ZapierReleaseTriggerMessageSignal to release just this one message and continue draining.
  • Throw ZapierAbortDrainSignal to 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.
For the full primitive API (leaseTriggerInboxMessages, ackTriggerInboxMessages, releaseTriggerInboxMessages), see the Triggers API reference.

Inbox management

List inboxes — find inboxes by status or key:

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

Your webhook endpoint receives the POST and immediately calls 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