Skip to main content

Why wasn't my callbackUrl called, and what does Wiro send to it?

How to set callbackUrl, what Wiro POSTs to it when a task finishes, and why a callback may not arrive.

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 POST with Content-Type: application/json. The body is the task record, the same kind of entry Task/Detail returns in tasklist, including status, pexit, outputs, totalcost and socketaccesstoken.

  • 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 http to https). Use the final URL and answer 200.

  • 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

  1. Answer 200 right away, then do the work.

  2. Skip tokens you've already handled. A retry can repeat a callback your server already received, so de-duplicate on socketaccesstoken.

  3. 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.

  4. 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

Did this answer your question?