Quick answer: add callbackUrl, spelled exactly like that, to your Run request. When the task finishes, Wiro POSTs the task as JSON to that URL. If nothing arrives, the usual causes are the spelling, a URL Wiro can't reach from the internet, or an endpoint that doesn't answer HTTP 200.
How to set it
Put callbackUrl in the Run request body next to the model's inputs, as a JSON key or a multipart/form-data field. Other spellings, such as callbackurl or callback_url, are ignored. Set it on every Run request that should notify you.
curl -X POST "https://api.wiro.ai/v1/Run/claude/fable-5" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"prompt": "Hello", "callbackUrl": "https://your-server.com/wiro/callback"}'
What Wiro sends
When: once the task has finished, whether it succeeded or failed, after its outputs are uploaded and the run is billed. A task cancelled while it was still queued never runs, so it sends no callback.
What: a
POSTwithContent-Type: application/json. The body is the task record, the same kind of entry Task/Detail returns intasklist, includingstatus,pexit,outputs,totalcostandsocketaccesstoken.No signature: the request carries no signature or secret, so anyone who knows your URL could call it. Confirm each callback with Task/Detail before you act on it (see below).
Why a callback doesn't arrive
Spelling: the key must be
callbackUrl.Reachability: Wiro's servers call your URL, so it must be reachable from the public internet; use HTTPS.
localhost, private network addresses and apps on a phone can't receive it. For a mobile app, point the callback at your own backend.Only HTTP 200 counts: 201, 204 and every other status count as a failed delivery, and so does a redirect, which isn't followed (for example from
httptohttps). Use the final URL and answer200.Body size limits: the body contains the whole task, and a long chat reply can make it large. Some frameworks reject big JSON bodies by default (Express's
express.json()allows 100 KB), and that rejection counts as a failure. Raise the limit on this route.Retries are short: Wiro tries 3 times, 2 seconds apart, then stops for good. If your server was down or restarting at that moment, the callback is lost, so keep polling Task/Detail as a fallback for tasks you haven't heard about.
Handle it safely
Answer
200right away, then do the work.Skip tokens you've already handled. A retry can repeat a callback your server already received, so de-duplicate on
socketaccesstoken.Fetch Task/Detail with that token and decide there:
pexit"0"means success, and any other value means the task failed. A callback arriving doesn't by itself mean success.A failed task isn't charged, even if its callback body shows a
totalcost. See Am I charged if my task fails or I cancel it?
A minimal Node.js (Express) receiver:
const express = require("express");
const app = express();
const seen = new Set();
app.post("/wiro/callback", express.json({ limit: "20mb" }), async (req, res) => {
res.sendStatus(200);
const token = req.body?.socketaccesstoken;
if (!token || seen.has(token)) return;
seen.add(token);
try {
const r = await fetch("https://api.wiro.ai/v1/Task/Detail", {
method: "POST",
headers: { "Content-Type": "application/json", "x-api-key": process.env.WIRO_API_KEY },
body: JSON.stringify({ tasktoken: token }),
});
const task = (await r.json()).tasklist?.[0];
if (!task) return; // unknown token: ignore it
if (task.pexit === "0") {
// success: use task.outputs
} else if (task.pexit) {
// failed
}
} catch (err) {
seen.delete(token); // let a retry or your polling pick it up
}
});
app.listen(3000);For a Signature Based project, also send x-nonce and x-signature (see Authentication).
Related
Other ways to get a result, such as polling and
/sync: How do I get the result of an API request?Following a task live: Why am I not getting any WebSocket messages for my task?
AI agents have their own webhooks, separate from
callbackUrl: see the agent API article.Docs: Webhook Callback, Run parameters and Task Detail.
