Skip to content

Callbacks

Pause a run until a webhook, a person or another system posts to a URL, with a timeout.

Updated View as Markdown

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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close