Triggers
A trigger is how an event enters palumb. Your app makes one call —
POST /v1/events — and palumb starts the matching workflow for every targeted
subscriber. That is the only edge your application drives directly.
curl -sS "$PALUMB_BASE_URL/v1/events" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "workflowId": "orderCreated", "to": { "subscriberId": "cust_1" }, "payload": { "orderId": "O-1" } }'The request
Section titled “The request”| Field | Required | Shape |
|---|---|---|
workflowId | ✓ | A workflow in your Bridge. Must match ^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9_]*$ (letters/digits/underscore — no dots or hyphens) |
to | ✓ | Exactly one of { subscriberId } or { topics: [...] } |
payload | — | Arbitrary JSON, forwarded verbatim to your workflow |
idempotencyKey | — | A string that makes the trigger safe to retry (see below) |
workflowIdnames one of your workflows directly. The trigger picks the workflow by name, so there is no in-handlerswitchover event types — the routing happens at the edge.tois the target. A singlesubscriberIdpasses straight through; atopicslist fans out (next section). Supplying neither or both is a422.- The tenant is resolved from the API key, never from the body.
The call returns 202 Accepted with a result per resolved subscriber:
{ "notifications": [ { "id": "<runId>", "subscriberId": "cust_1", "status": "dispatched" }] }Keep the id — it is the run id, the handle you use to
signal a waiting run.
The topic fan-out
Section titled “The topic fan-out”When to is { topics: [...] }, palumb expands every topic into its subscribers
and deduplicates by subscriber — a person on three of the targeted topics is
notified once, not three times.
Then palumb starts one workflow run per subscriber. This matters:
- Your workflow always sees exactly one
subscriber— never a list to loop. - One subscriber’s failure does not abort the fan-out. Each run is independent; a bad address for one person doesn’t stop the rest.
flowchart LR
E["POST /v1/events<br/>to: { topics: [ops, oncall] }"]
R["resolve + dedup<br/>by subscriber"]
R1["run · alice"]
R2["run · bob"]
R3["run · carol"]
E --> R --> R1 & R2 & R3
This is the shape behind the escalation ladder:
topic fan-out gives you one run per person; the per-step
{ to } override lets a single run climb to a
supervisor.
The ingress ledger
Section titled “The ingress ledger”Each dispatched subscriber gets one NotificationLog row — the ingress
ledger. Its id is the run id. Its status moves through:
| Status | When |
|---|---|
received | The row is written, before dispatch is attempted |
dispatched | The run was submitted to the engine (records the invocation id) |
failed | Submitting the run threw |
This ledger records what entered palumb. It is deliberately separate from the
egress DeliveryAttempt ledger, because a
digest collapses many ingress events into one
delivery — the two cannot share a row.
Idempotency
Section titled “Idempotency”Pass an idempotencyKey to make a trigger safe to retry. palumb derives a
deterministic run id from (tenantId, workflowId, subscriberId, idempotencyKey)
(a UUIDv5), so a retried call lands on the same run instead of starting a
second one. If that run was already dispatched, palumb short-circuits and
returns the prior result — no duplicate run, no duplicate sends.
Without an idempotencyKey, every call starts a fresh run (at-least-once): fine
for genuinely distinct events, risky for retries of the same event.
Waiting and signalling
Section titled “Waiting and signalling”A workflow can suspend on
step.waitForEvent
until your system reports something happened. You resolve that wait by signalling
the run:
curl -sS "$PALUMB_BASE_URL/v1/events/$RUN_ID/signal" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "event": "userEngaged", "data": { "source": "first-login" } }'event must match the name the workflow is waiting for; data is delivered as
the waitForEvent result. A run id that doesn’t belong to your tenant is a 404.
This events-in / sends-out model keeps palumb from ever calling back into your
backend to make a decision — see
event-driven flows.
Error cases
Section titled “Error cases”| Situation | Status |
|---|---|
Neither or both of subscriberId / topics | 422 |
| No Bridge registered for the tenant | 422 |
workflowId fails the identifier regex, oversized field | 400 |
| Signal for an unknown run (or another tenant’s) | 404 |
Related
Section titled “Related”- Writing a workflow — the handler side.
- Architecture › The request-to-delivery loop
- REST API — the
POST /v1/eventscontract.