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

# Develop a trigger locally

> Watch trigger events arrive on your laptop with no public URL, and rehearse signed deliveries against a receiver on localhost.

<Info>**Uses:** CLI · Python · TypeScript</Info>

While you are building, you usually don't have a public HTTPS endpoint for a destination. The live **stream** (`GET /v1/triggers/subscribe`) fills that gap: it sends you the account's trigger events as they are captured, over one outbound connection.

<Warning>
  **The stream is for development, not production.** It is unsigned, it carries no delivery guarantees, and its `delivery` field always reads `"pending"`. Production delivery is a [destination plus signature verification](/examples/trigger-webhook).
</Warning>

## 1. Watch events arrive

Create and enable a trigger first ([step 2 of the webhook recipe](/examples/trigger-webhook#2-create-the-trigger-and-enable-it)), then open a stream filtered to it and make a change in the connected app.

<CodeGroup>
  ```bash CLI theme={null}
  engini triggers listen --trigger ti_DG9q7IlQO0MeghC4IEzP19
  # {"id":"te_…","trigger_id":"ti_…","occurred_at":"…","payload":{…},"delivery":"pending"}
  ```

  ```python Python theme={null}
  for event in client.triggers.subscribe(trigger_id="ti_DG9q7IlQO0MeghC4IEzP19"):
      print(event["id"], event["payload"])
  ```

  ```typescript TypeScript theme={null}
  for await (const event of client.triggers.subscribe({ triggerId: "ti_DG9q7IlQO0MeghC4IEzP19" })) {
    console.log(event.id, event.payload);
  }
  ```
</CodeGroup>

`listen` prints one compact JSON line per event, so it pipes straight into `jq`:

```bash theme={null}
engini triggers listen --app monday | jq -c '{id, name: .payload.name}'
```

Filter by `--trigger`, `--slug` (a trigger type) or `--app`. Stop with `Ctrl-C` (from CLI `0.23.0`; it prints a summary line and exits `0`).

## 2. Rehearse a signed delivery on localhost

`--forward` POSTs every streamed event to a local URL in the same envelope a real delivery uses. Add `--sign-with` and each POST carries a genuine `X-Engini-Signature`, so the verification code you will deploy is exactly the code you are testing - no tunnel needed.

```bash theme={null}
# terminal 1: your receiver (e.g. the Flask or Express one from the webhook recipe)
ENGINI_SIGNING_SECRET=esec_local_dev_secret python app.py

# terminal 2: stream and forward, signed with the same secret
engini triggers listen --trigger ti_DG9q7IlQO0MeghC4IEzP19 \
  --forward http://localhost:8080/hooks/engini --sign-with esec_local_dev_secret
```

* The secret only has to match between the two terminals - it does not need to be a real destination secret. `ENGINI_TRIGGER_SIGNING_SECRET` works in place of `--sign-with`.
* Without a secret, the forwarded POSTs are unsigned, and a correct receiver rejects them.
* The forwarded body always has `attempt: 1`, and `trigger_slug` is usually `null` - check the slug against your trigger id, not the body, if your receiver branches on it.
* A failed forward never stops the stream. When the stream ends on its own with any failed forwards, `listen` exits `1`, and the summary line `{"events": n, "forwarded": n, "failed": n}` shows how many.

## 3. Know how a stream ends

| Ending | What happens |
| - | - |
| One hour of wall-clock time | The gateway cuts every stream at one hour - an absolute cap, not an idle timeout. The CLI and both SDKs reconnect with `?since=<last event id>`, so nothing is duplicated or skipped |
| The resume cursor was pruned by retention | `410 SINCE_PRUNED` - the SDKs raise `EnginiTriggerGapError`, the CLI exits `4`. Read the gap from the event log and subscribe again |
| More than 10 streams open on the account | `429` - the SDKs wait out `Retry-After` and try again |
| Credentials revoked mid-stream | Stops hard with an auth error |

Pass `--no-resume` (CLI), `auto_resume=False` (Python) or `autoResume: false` (TypeScript) to handle the one-hour cut yourself.

## When you're ready for production

Register a real destination and deploy the same receiver behind HTTPS: [Receive trigger events on a webhook](/examples/trigger-webhook). The verification you rehearsed here is unchanged.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.