Skip to content

Activating an External Callback

An External Callback block gives one point in a flow its own URL. Calling that URL either starts a new run of the flow, when the block is the first step, or lets a run that is paused at the block continue. The call accepts GET or POST, carries whatever data the flow needs, and answers with the id of the run it started or continued.

Endpoint

https://api.flowrunner.ai/{workspace-id}/{api-key}/automation/flow/{flow-id}/trigger/{block-id}/activate
Placeholder Value Where it comes from
{workspace-id} The workspace the flow belongs to Workspace Settings ▸ General, Credentials section
{api-key} The workspace's API key Same section, beside the Workspace ID
{flow-id} The flow that contains the block Filled in for you - see below
{block-id} This particular External Callback block Filled in for you - see below

You never assemble this address yourself. Every External Callback block shows its complete URL, all four segments filled in, in its Callback URL field - see Get the address. Every block has its own URL; they are not interchangeable.

Calling with POST

The data you send becomes the block's data, read by later steps under the alias set on the block. With POST it goes in the body, sent with Content-Type: application/json:

curl --request POST \
  --url "<Callback URL>" \
  --header "Content-Type: application/json" \
  --data '{
    "event": "payment.succeeded",
    "orderId": "ORD-9001",
    "amount": 4999
  }'

The body becomes the block's data, whole. A call with no data at all - the bare address - is valid. On a POST, query parameters are ignored, with one exception: execution picks which waiting run to continue (executionId is accepted as an alias). It is required when the block sits after the first step, and ignored when the block is the first step - see When more than one run is waiting:

curl --request POST \
  --url "<Callback URL>?execution=4CE29875-0BD4-4AE4-B75C-60FCC55F4436" \
  --header "Content-Type: application/json" \
  --data '{
    "event": "payment.succeeded",
    "orderId": "ORD-9001",
    "amount": 4999
  }'

Calling with GET

With GET, the same data goes in the query string, and no headers are needed:

curl "<Callback URL>?event=payment.succeeded&orderId=ORD-9001&amount=4999"

Every query parameter except execution becomes one field of the block's data, under its own name. The execution parameter works exactly as on a POST. Any fetch of the URL is a real call - a chat link preview or an uptime monitor included - so share a Callback URL as text, never a clickable link.

Response

An accepted call returns HTTP 200 with the id of the run it started or continued:

{ "executionId": "4CE29875-0BD4-4AE4-B75C-60FCC55F4436" }

With execution=all, executionId lists every run that continued, comma-separated. With execution=all and nothing waiting, the call still succeeds and the id is null:

{ "executionId": null }

A call can also be accepted and still do nothing. A callback block can carry a condition deciding whether an incoming call counts; when the condition is false the call returns HTTP 200 and no run starts or continues. If a call appears to succeed but the flow does not move, check that condition first.

Errors

A refused call comes back as HTTP 400 with an error code and a message:

{
  "code": 28056,
  "message": "Your trigger activation request format can be used only for a flow that starts with a trigger. Consider adding \"execution\" parameter to activate a \"middle of the flow\" trigger."
}

The call did not identify a waiting run

Code What FlowRunner tells you What to do
28056 The request format can be used only for a flow that starts with a trigger; consider adding the execution parameter to activate a middle-of-the-flow trigger You called a callback that sits later in a flow. Add execution
28059 Pending trigger was not found. Ensure that passed execution ID is valid and flow execution is waiting on this trigger The id is wrong, or that run is not sitting at this block
28060 Pending trigger was not found. Ensure that flow execution is waiting on this trigger You used execution=any and nothing was waiting
28100 Execution with that ID already exists This run has already been let through this callback. A run passes a given block once; a later callback in the same flow is a separate wait with its own activation

The flow or the block cannot be reached

Code What FlowRunner tells you What to do
28010 Trigger with that flow element ID is not found in that flow; ensure the flow and trigger names are correct and that the flow has a live version (or a paused one, when the trigger sits mid-flow) Re-copy the Callback URL - the block's Callback URL field carries the trigger's own id, which is not the id shown elsewhere for the block - and start the version so it is LIVE
28011 Trigger with that name is not found in that flow; same advice As above
28042 The flow version is not enabled, so its trigger cannot be executed Set the version LIVE
28093 Available only when the flow is in the LIVE state Start the version you are calling so it is LIVE - see Running Flows

The run is no longer there

Code What FlowRunner tells you What to do
28068 Execution context with that id not found The id is wrong, or the run finished and was cleared
28136 That execution is already terminated The run ended before your call arrived

The caller is not allowed

Code What FlowRunner tells you What to do
28025 Unable to execute the callback. User is missing required security role(s) Call with a user token carrying the required role
28061 The user token is invalid Obtain a fresh token

The request itself, or too many calls

Code What it means
9000 No workspace with that id - re-copy the Callback URL
999 / 997 / 995 / 998 A rate limit (per second / minute / day / in-flight) - back off and retry
28083 / 28087 / 28132 / 28086 A plan limit (actions, concurrent runs, executions, active flows) - wait for capacity or raise the plan (see Billing)

When to use it

An external callback is useful in two scenarios:

  1. Your flow may need to start in response to an external event - a payment provider announcing a charge, a form posting a submission, your own application saying the work it was given is finished.
  2. Your flow may need to wait for something outside it to finish before it resumes - a user approves a transaction, confirms email copy, and so on.

The External Callback block is the way to enable these types of behavior. Place it at the beginning of your flow or in the middle, depending on the use case. This page is about making the call. Building the flow around it is covered in Waiting on an External System.

This is the callback block, not every trigger

Other triggers in FlowRunner come from the services they belong to, and are activated by those services in their own way. This page applies to the External Callback block, which is FlowRunner's own.

Get the address

Select the External Callback block. Its Callback URL field holds the whole address, with a copy button beside it. Copy it and give it to whichever system needs to activate the trigger in the flow.

The External Callback block selected, its settings showing the generated Callback URL with a copy button.

For real traffic, a version of the flow has to be LIVE. Call the address when no version is LIVE and you get error 28010 - not because your request was malformed, but because there is nothing live to receive it. Starting a version is covered in Running Flows.

While you are building, two things make the URL answer without the flow being LIVE:

  • Learning Mode captures one sample request and records its shape, so the fields become pickable in later steps.
  • Run Block on the callback starts a debug execution that waits for your call.

Starting a run, or continuing one

Both scenarios make the same call. What differs is what the call finds.

If the callback is the first step, there is no run yet. Your call creates one, and it begins with the data you sent.

If the callback sits further along, the run was already under way - started by whatever starts that flow - and it has arrived at the block and stopped, waiting for you. Your call lets it carry on, with the data you sent now available to the steps that follow.

Only the second case asks more of you: a waiting run has to be identified.

When more than one run is waiting

Picture an order-processing flow. It runs every few minutes, and each run reaches a callback and waits for its own payment to clear. By mid-morning three runs are sitting at that same block, each waiting for a different order.

Now a payment clears and the provider calls your address. Which of those three should continue?

The address cannot answer that on its own - it names the flow and the block, not the run. So the call says which one, using the execution query parameter:

POST  <Callback URL>?execution={execution-id}

Which value you use depends on whether the waiting runs are worth telling apart:

Value Continues Use it when
execution={execution-id} that one run The runs are distinct - each waits for its own order, its own customer, its own document
execution=any one waiting run, FlowRunner's choice The runs are interchangeable and it does not matter which continues
execution=all every run waiting at this block One event releases all of them at once

Where the execution id comes from

A run's id exists only once the run is under way, so it cannot be written into anything in advance. It reaches the caller one of three ways.

The flow hands out its own address. This works however the run began - a schedule, an API call, another flow. Every run knows its own Execution ID in the Expression Editor under Flow Context, so the step that calls the outside system composes the return address from the Callback URL and that id, and sends it. Whatever comes back points at this run.

The Expression Editor of a step before the callback: an External Callback URL reference under External Callback URLs, and Execution ID under Flow Context, joined into one return address.

The caller started the run and was told. A system that started the run through the Call Flow (Non-Blocking) API received executionId in the response, and can quote it back.

Nobody can carry a per-run address. A webhook configured once in a vendor's dashboard posts to one fixed URL and cannot append anything. Use execution=any on it.

Waiting on an External System works all three through with diagrams.