Minimal example
-
Step block:
execute.tscalls an external API and returns an outcome:execute.ts -
Trigger block:
activate.tsregisters the webhook,trigger.tsfires the run:activate.tstrigger.ts
Trigger lifecycle
A trigger manages an external subscription. Attio drives three distinct phases: Key points:activateanddeactivateeach run once per workflow version. When a workspace member edits and re-enables the workflow, Attio deactivates the old version and activates the new one. You may receive events for the oldtriggerCallbackUrlduring this window.triggerruns once per incoming request totriggerCallbackUrl. It should be fast and side-effect-free beyond deciding whether to start a run. Any heavy work belongs in a step block.uniqueActivationIdis stable across retries of the sameactivatecall. Store it alongside your webhook registration so you can identify and clean it up indeactivate.- If
activatereturns{type: "error"}, the workflow is not enabled anddeactivateis never called.
Step lifecycle
A step runs inline during a workflow run. The basic path is a single call toexecute:
Key points:
uniqueExecutionIdis stable across retries of the sameexecutecall. Use it as an idempotency key when calling external APIs to avoid duplicate side-effects.- Returning
{type: "defer"}suspends the block; the run pauses until an external service POSTs tofinishCallbackUrl. No server resources are held while waiting. finishis only required whenexecutemay return{type: "defer"}. Ifexecutenever defers, the file is optional.- A deferred block can receive multiple callbacks before resolving; return
{type: "no-op"}to stay deferred and wait for another POST. retryable: trueon an{type: "error"}return tells Attio it may safely retry the execution. Only set this when the operation is genuinely idempotent.
Defer
Usedefer when your block must wait for something that happens outside the current HTTP request: a human approval, a slow background job, or a webhook from an external system. Unlike returning an outcome immediately, defer tells Attio to suspend the run entirely until an external signal arrives.
The flow in detail:
execute()runs. Before returning, registermetadata.finishCallbackUrlwith the external service; this is the URL it will POST to when the work is done.- Return
{type: "defer"}. Attio immediately suspends the block. The workflow run is checkpointed and paused; no server resources are held while waiting. There is no timeout; the block can stay deferred indefinitely. - External event fires. The external service POSTs to
finishCallbackUrlwith any payload it wants. - Attio calls
finish()with the incoming request. The handler reads the payload and decides what to do. - Resolve or stay deferred. Return an outcome / exit / error to resume the run, or return
{type: "no-op"}to stay deferred and wait for the next POST. The external service can POST multiple times before the block resolves, which is useful for polling-style callbacks or multi-step approvals.
finishCallbackUrl is unique per execution. Register it with the external service inside execute(), not before. Do not share it across runs or blocks.execute.ts
finish.ts
Configurator
configurator.tsx is not part of the runtime lifecycle. It runs in the browser, inside the workflow editor, when a workspace member is configuring the block. It has no access to server-side APIs and should never perform side-effects. See Configurator.
See also
- Registering a trigger: trigger activate handler
- Receiving a trigger event: trigger event handler
- Deactivating a trigger: trigger deactivate handler
- Executing a step: step execute handler
- Finishing a deferred step: deferred step handler