A callback is a step that waits for someone to post to a URL: a webhook from another system, an approval link in an email, a person finishing a task. The run suspends while it waits, for as long as the timeout allows.
import { CallbackTimeout } from "@ezgh/workflows"
const cb = await step.createCallback<{ approved: boolean }>("approval", { timeoutMs: 3 * 86_400_000 })
await step.run("ask", () => email.send(manager, `Approve? POST to ${cb.url}`))
try {
const { approved } = await cb.wait()
} catch (e) {
if (e instanceof CallbackTimeout) { /* nobody answered in time */ }
}cb, err := workflows.CreateCallback(ctx, "approval", 3*24*time.Hour)
_, err = workflows.Run(ctx, "ask", func(sc workflows.StepContext) (bool, error) {
return true, mail.Send(sc, manager, "Approve? POST to "+cb.URL)
})
decision, err := workflows.WaitCallback[Decision](ctx, cb)
if errors.Is(err, workflows.ErrCallbackTimeout) { /* nobody answered in time */ }from ezgh_workflows import CallbackTimeoutError
cb = await ctx.step.create_callback("approval", timeout="3d")
await ctx.step.run("ask", lambda: send_email(manager, f"Approve? POST to {cb.url}"))
try:
decision = await cb.wait()
except CallbackTimeoutError:
... # nobody answered in time- The URL is
https://workflows-api.ezghcloud.com/public/v1/callbacks/<token>. It’s recorded before your code sees it, so hand it out from a step, and a replay rebuilds the same URL. - The timeout is 1 second to a year, 7 days by default, and starts when the callback is
created. When it passes, waiting throws a callback timeout error (code
workflows.callback_timeout), which the workflow can catch. It isn’t retried. - Cancelling the run ends its waiting callbacks at once.
Complete a callback
By its URL, with no credential: the token in the URL is the only one, so treat the URL as a
secret. Post a JSON body, at most 1 MiB, with Content-Type: application/json:
curl -X POST "$CALLBACK_URL" -H "Content-Type: application/json" -d '{"approved": true}'The body becomes the callback step’s result. The SDKs have a helper that posts to a URL:
completeCallbackURL(url, value) in TypeScript, workflows.CompleteCallbackURL(ctx, httpClient, url, value) in Go, and complete_callback_url(url, value) in Python.
As an authenticated caller, by run ID and step ID, with
CompleteCallback and workflows.callbacks.complete on
the workflow. It answers the run.
| Status | Meaning |
|---|---|
200 |
Completed. The first completion wins; a repeat with the same body also answers 200 |
400 |
The body isn’t valid JSON |
404 |
No such callback |
409 callback_completed |
Already completed with a different body |
410 callback_expired |
The callback timed out, or its run was cancelled or ended |
413 |
The body is over 1 MiB |
415 |
The body isn’t application/json |
429 |
Too many completions from your address: completing by URL allows 20 a second per client IP address, in bursts of up to 60 |
Completions by URL aren’t recorded in Trails; authenticated completions are recorded when they’re refused. Callback bodies never appear in Trails.