Let your system decide a step
Let a process step ask your own system and wait for its answer: your ERP checks a credit limit, prints a label, or approves a return. Picked calls your endpoint, the work sleeps, and your system calls back to say which way it goes.
When to use this
Use this recipe when a decision belongs to a system Picked doesn't run, and that system can answer in its own time. Each call carries its own callback address, so your system never has to work out which piece of work it is answering.
If your system only needs to send a signal that several waiting processes listen for, resume a process with a signal instead.
Build the step
In the process builder, open the step that should ask:
- Set its input to Wait.
- Under What ends the wait, choose An answer from your system.
- Fill in Your endpoint: the
httpsaddress Picked calls. Write the host out: it can't come from a variable. - Optional: Headers, such as an
Authorizationheader. Send an API key as{{ secret.name }}after adding it under Settings → Secrets, never as plain text. Headers are sent as written: only{{ secret.name }}is filled in, not{{ variables }}. - Optional: Body, the fields Picked sends. Values can use
{{ variables }}from the work, such as{{ order_ref }}. - Give up after (minutes), up to a week (10080 minutes).
- Draw an outgoing edge from the step for each outcome your system can send back, such as
approvedandrejected, plus one plain edge for anything else.timed_outandcall_failedcan have their own edges too.
Publish the process. Work that reaches the step now calls your endpoint and sleeps there.
An AI agent connected to Picked can build the same step: ask it for an external action step.
What your endpoint receives
A POST with your body fields as JSON, and a picked object beside them:
POST /picked/check HTTP/1.1
Host: erp.example.com
Content-Type: application/json
Authorization: Bearer <your key>
{
"order": "SO-48211",
"picked": {
"callback_url": "https://picked.nl/api/v1/process-runs/callbacks/q7Lx2VnR9bT4wE6yH1mK8sD3fG5jP0cZa1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6",
"expires_at": "2026-10-14T09:30:00+00:00",
"run_id": 5512,
"step": "ask_erp"
}
}
Answer the call itself with any 2xx within 10 seconds, and do the work afterwards. A call that times out, or answers 5xx, 408 or 429, is tried three times in all; any other 4xx isn't tried again. If none gets through, the step continues with the outcome call_failed.
Call back
POST to the callback_url, before expires_at. It needs no API token: the address is the credential, and it works for this one piece of work only. Keep it as you would a password.
outcome(optional): which edge the work takes, in lowercase letters, digits and_. Without it the outcome iscompleted. An outcome no edge names takes the step's plain edge.data(optional): any JSON object, up to 100 top-level keys and 64 KB. The work can read it from then on aswait_payload, and the step's outputs take their fields from it: see below.
curl -X POST "https://picked.nl/api/v1/process-runs/callbacks/q7Lx2VnR9bT4wE6yH1mK8sD3fG5jP0cZa1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"outcome": "approved",
"data": { "credit_limit": 2500, "approved_by": "j.devries" }
}'
HTTP/1.1 200 OK
Content-Type: application/json
{ "status": "woken" }
Use the answer in later steps
To show or use a field of the answer in later steps, not only branch on it, give the step an output for it. In the builder, under the step's Outputs, add one per field:
- Name: what later steps call it, such as
suggested_bin. Not a name Picked keeps for itself, such asorder_idorwait_payload. - Type: text, number, true/false, object or list.
- Field in the answer: where it is in
data, with dots for nested fields, such asbinorlabel.id. - Default (optional): the value when the answer leaves the field out, or when the step timed out.
Later steps read it as {{ steps.<step>.suggested_bin }}: in their text, in action settings, in conditions, and in the body of a later call to your system.
A callback whose data lacks a field without a default, or has one of another type, answers 422 naming the field (data.bin), and the work keeps waiting: send it again corrected. JSON types count as sent: "4" is text, not a number.
Read the answer
| Status | Meaning |
|---|---|
200 |
woken: the work carries on. repeated: this callback already woke it, and a repeat changes nothing. Retrying a callback is always safe. |
503 |
temporarily_unavailable: the work can't carry on yet, for want of stock or capacity. It is still waiting: call again after the Retry-After seconds. |
410 |
gone: the step stopped waiting: it timed out, the call failed, or someone ended the wait. The work has gone its own way, and this address no longer works. |
404 |
not_found: no work waits on this address. |
422 |
The outcome isn't a name an edge could have, or is one Picked sets itself (timed_out, call_failed, force_resumed, released), or data is too large, not an object, or lacks a field the step reads, or has one of another type. |
Errors lists every code, and Rate limits what to do on a 429.
Testing
Callbacks can't be dry-run: a Picked-Dry-Run: true header answers 400 here and nothing is done. Test with a process of your own: point its step at an endpoint you control, let a piece of work reach the step, and call the callback_url your endpoint received.