Skip to content

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.

Terminal window
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" } }'
FieldRequiredShape
workflowIdA workflow in your Bridge. Must match ^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9_]*$ (letters/digits/underscore — no dots or hyphens)
toExactly one of { subscriberId } or { topics: [...] }
payloadArbitrary JSON, forwarded verbatim to your workflow
idempotencyKeyA string that makes the trigger safe to retry (see below)
  • workflowId names one of your workflows directly. The trigger picks the workflow by name, so there is no in-handler switch over event types — the routing happens at the edge.
  • to is the target. A single subscriberId passes straight through; a topics list fans out (next section). Supplying neither or both is a 422.
  • 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.

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.

Each dispatched subscriber gets one NotificationLog row — the ingress ledger. Its id is the run id. Its status moves through:

StatusWhen
receivedThe row is written, before dispatch is attempted
dispatchedThe run was submitted to the engine (records the invocation id)
failedSubmitting 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.

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.

A workflow can suspend on step.waitForEvent until your system reports something happened. You resolve that wait by signalling the run:

Terminal window
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.

SituationStatus
Neither or both of subscriberId / topics422
No Bridge registered for the tenant422
workflowId fails the identifier regex, oversized field400
Signal for an unknown run (or another tenant’s)404