Transport
Webhook push vs. worker pull. How the handoff crosses the boundary.
Overview
Transport is how the handoff crosses the boundary. It is the mechanism by which CueAPI delivers a cue's declared intent to your agent. A cue is the intent. An execution is the handoff boundary. Transport is the path across it. CueAPI supports two transport modes:
Webhook (push)
CueAPI sends a signed HTTP POST to your callback URL. Default transport.
Worker (pull)
Your local daemon polls CueAPI for pending executions and runs handler scripts.
Webhook transport
The default. CueAPI POSTs a signed payload to your callback URL when the cue fires.
{
"name": "daily-sync",
"schedule": {"cron": "0 9 * * *"},
"callback": {
"url": "https://api.yourapp.com/webhook",
"method": "POST"
}
}Requirements:
- Public HTTPS URL (HTTP allowed in development only)
- Returns 2xx for success
- Must respond within 30 seconds
What CueAPI sends:
POST https://api.yourapp.com/webhook
X-CueAPI-Signature: v1=abc123...
X-CueAPI-Timestamp: 1710340800
X-CueAPI-Cue-Id: cue_abc123def456
X-CueAPI-Execution-Id: exec-uuid
Content-Type: application/json
{"task": "daily-sync", "kind": "scheduled_task", ...}
Worker transport
For machines without a public URL. A daemon on your machine polls for executions and runs local handler scripts.
{
"name": "draft-linkedin",
"schedule": {"cron": "0 9 * * 1-5"},
"transport": "worker",
"payload": {
"task": "draft-linkedin",
"kind": "agent_turn"
}
}How it works:
- CueAPI creates executions in
pendingstatus - Your worker daemon polls
GET /v1/executions/claimable - Worker claims an execution via
POST /v1/executions/{id}/claim - Worker runs the matched handler script
- Worker reports the outcome via
POST /v1/executions/{id}/outcome
Requirements:
cueapi-workerdaemon running on your machine, or a custom polling script- YAML config mapping
payload.taskto handler scripts (if using the daemon) - API key for authentication
Worker loop in JavaScript
If you prefer to write your own worker instead of using the daemon:
const API = "https://api.cueapi.ai/v1";
const headers = {
"Authorization": "Bearer cue_sk_YOUR_KEY",
"Content-Type": "application/json",
};
async function workerLoop() {
while (true) {
// 1. Poll for claimable executions
const poll = await fetch(
`${API}/executions/claimable?task=my-task`,
{ headers }
);
const { executions } = await poll.json();
for (const exec of executions) {
// 2. Claim the execution
const claim = await fetch(
`${API}/executions/${exec.id}/claim`,
{ method: "POST", headers }
);
if (!claim.ok) continue; // already claimed
// 3. Do the work
let success = true;
let result = "";
try {
result = await doWork(exec.payload);
} catch (err) {
success = false;
result = err.message;
}
// 4. Report outcome
await fetch(`${API}/executions/${exec.id}/outcome`, {
method: "POST",
headers,
body: JSON.stringify({
success,
result,
...(success ? {} : { error: result }),
}),
});
}
// Wait before next poll
await new Promise((r) => setTimeout(r, 5000));
}
}Choosing a transport
| Consideration | Webhook | Worker |
|---|---|---|
| Public URL required | Yes | No |
| Runs on local machine | No | Yes |
| Handler language | Any (HTTP endpoint) | Any (shell command) |
| Latency | Immediate | Polling interval (5s default) |
| Retries | Automatic (3 attempts) | Manual via outcome reporting |
| Signing | HMAC-SHA256 | N/A (API key auth) |
| Best for | Cloud services, APIs | Local scripts, AI agents |
Specifying transport
Set transport at cue creation:
{
"name": "my-cue",
"schedule": {"cron": "0 9 * * *"},
"transport": "worker",
"payload": {"task": "my-handler"}
}You can also nest transport inside callback:
{
"callback": {"transport": "worker"}
}Info
Webhook cues require a callback URL. Worker cues do not need a callback URL, but can optionally have one.