Integracije
Async Tools (Callback Transport)
Let the HelpStack AI hand a job to your system, and pick the conversation back up when your system says it is done.
Quick facts#
| Tool type | CALLBACK_JOB |
| You receive | One POST to a URL you choose |
| You reply with | One POST back to https://helpstack.eu/api/deferred-tools/callback |
| Progress (optional) | The same POST, sending progress instead of ok |
| Auth, us → you | Whatever headers you configure on the tool (stored encrypted) |
| Auth, you → us | A single-use token we send you in the job |
| Timeout | 5 seconds to 1 hour, you choose |
| Feature flag | deferredTools (off by default; ask us to enable it) |
When to use this, and when not to#
Almost every tool should be a normal one that answers inside the same generation. Reach for an async tool only when the work genuinely outlives the request: a rebuild, a deploy, a bulk update, an image or video render, anything queued behind your own workers.
The test: can your endpoint hold the HTTP connection open until the answer exists, without a customer noticing the wait? If yes, use a normal
SERVER_SIDEtool. You get a simpler contract and the answer arrives in the same reply.
The async machinery costs you a callback endpoint, a token to keep safe, and a timeout to think about. It is worth it when the alternative is holding a connection open for two minutes; it is not worth it to save 200ms.
The shape of it#
customer asks something
│
▼
AI calls your tool ──POST──▶ your system (accepts, returns 2xx, starts work)
│ │
│ conversation parks │ … work happens …
│ (customer is told │
│ we are on it) ▼
│ POST back to us
▼ │
conversation resumes ◀────────────┘
Two independent requests. We do not hold a connection open waiting for you, and you must not try to answer synchronously.
1. What we send you#
When the AI decides to call your tool, we POST to your configured URL:
{
"loopId": "cmt7oqq9l0028oh2ctjxug62k",
"tool": "apply_layout",
"input": { "layout": "wide", "pageId": "home" },
"callbackUrl": "https://helpstack.eu/api/deferred-tools/callback",
"callbackToken": "9f2c…64 hex chars…",
"expiresAt": "2026-09-01T10:31:00.000Z"
}
| Field | Meaning |
|---|---|
loopId | Our id for this job. Useful in your logs; you do not send it back. |
tool | The tool name, so one endpoint can serve several tools. |
input | The parameters the AI filled in, matching the tool's parametersSchema. |
callbackUrl | Where to report back. Read it from the payload rather than hardcoding it. |
callbackToken | Single use. Your authentication when you call back. Treat it as a secret. |
expiresAt | After this, the job is timed out and your callback will be refused. |
Respond 2xx quickly — within 15 seconds — to acknowledge that you have
accepted the job. Do the work afterwards, not before responding.
A non-2xx, a timeout or a connection refusal means the job never started: we mark it failed and put a human on the conversation immediately, because the customer has already been told we are on it.
Anything you return in the body is ignored. The acknowledgement is a receipt, not a result.
2. Authenticating us to you#
Configure request headers on the tool (Settings → Agent Tools). They are stored encrypted and sent with every dispatch:
{ "Authorization": "Bearer your-shared-secret" }
Verify that header. Your endpoint will be on the public internet and its URL is not a secret. If it is unauthenticated, anyone who learns the URL can make your system do work.
3. Calling us back#
When the work finishes (or fails), POST to the callbackUrl:
curl -X POST https://helpstack.eu/api/deferred-tools/callback \
-H 'Content-Type: application/json' \
-d '{
"token": "9f2c…the token from the job…",
"ok": true,
"summary": "The homepage layout is now wide. It may take a minute to appear.",
"payload": { "revision": 4128 }
}'
| Field | Required | Notes |
|---|---|---|
token | yes | The callbackToken from the job. Single use. |
ok | for a completion | true if the work succeeded, false if it could not be done. Omit it to send a progress report instead (see below). |
summary | no | One or two plain sentences, max 2000 chars. This is the only field the AI ever sees. |
payload | no | Structured data for your own records. Never shown to the AI or the customer. |
You get 202 Accepted. Resuming the conversation happens on our side
afterwards, so do not retry on a slow response — retry only on a network
error or a 5xx.
Writing a good summary
The AI rewrites this for the customer in their own language, so write plain facts, not customer-facing prose:
- Good:
"Layout set to wide. Cached pages refresh within 60 seconds." - Good:
"Could not apply: the page is locked by another editor." - Bad:
"✅ SUCCESS! Your beautiful new layout is live!!"— tone is ours to set. - Bad:
"OK"— true, but the customer learns nothing.
Do not put instructions in it. The summary is treated as untrusted text and fenced before it reaches the model; text like "ignore your previous instructions" will not be obeyed, and attempts to do so are simply wasted.
When ok is false
We do not guess at a customer-facing explanation. The loop is marked failed, a
human is put on the conversation, and your summary is what they read. Write it
for a colleague: say what failed and why, plainly.
Reporting progress while you work (optional)
For a job that takes more than a few seconds, you can tell the visitor what is
happening. Send the SAME token with a progress label and no ok:
curl -X POST https://helpstack.eu/api/deferred-tools/callback \
-H 'Content-Type: application/json' \
-d '{ "token": "9f2c…", "progress": "Rebuilding pages" }'
They see that text instead of the generic "Working on it…". Send as many as you like; the last one wins.
| Does not settle the job | The token stays usable for the completion that follows |
| Does not reach the AI | The label renders as chat text only, and is never fed to a model |
| Max length | 120 characters — it renders on one line |
Omitting ok | Is what makes it a progress report. Sending ok at all, even false, FINISHES the job |
⚠️ ok: false is a completion, not a progress report. If you mean "still
going, but something went wrong", send a progress label. Sending ok: false
ends the job and puts a human on the conversation.
Progress is entirely optional. A job that only ever sends its completion behaves exactly as before.
4. Timeouts#
Set deadlineSeconds on the tool, between 5 seconds and 1 hour. Pick a
number a little above your realistic worst case, not your average.
When it expires, one of two things happens, depending on how the tool is configured:
onDeadline | What happens |
|---|---|
ESCALATE | A human is put on the conversation. The customer is told nothing automatically. |
FAIL | The AI tells the customer plainly that it could not be completed and invites them to reply. |
A late callback after the deadline is refused, so a job that overruns is not merely slow — its result is dropped. If your work can take ten minutes, say ten minutes.
There are no automatic retries. If your endpoint accepts a job and then
silently dies, the deadline is what rescues the conversation. This is deliberate:
retrying a POST can repeat a side effect, and only you know whether your
endpoint is safe to call twice.
5. Making your endpoint safe#
Both directions are on the public internet. Four things matter:
- Verify our auth header on every dispatch (see §2).
- Treat
inputas untrusted. It comes from an AI acting on a customer's words. Validate it as strictly as you would a form submission from a stranger — allowlist the values you accept rather than blocklisting ones you do not. - Make your work idempotent if you can. We do not retry, but networks are networks, and an idempotent endpoint is one less thing to reason about.
- Do not echo the customer's words back in
summaryunless you need to. It is the shortest path between a customer's message and a prompt.
What we do at our end
- Your URL is checked before every dispatch and refused if it points at a private, loopback, link-local or cloud-metadata address, including via DNS. A public hostname whose DNS answer is internal is refused too.
- The callback token is 32 bytes of entropy, stored only as a SHA-256 hash, single use, and valid only while the job is open. A replay, a token for an already-finished job, and an unknown token are all refused identically, so the endpoint cannot be probed to learn which tokens existed.
- Two callbacks racing each other resolve to one: the customer is never replied to twice.
6. A minimal implementation#
// POST /helpstack/jobs — the endpoint you configure on the tool
export async function POST(req: Request) {
if (req.headers.get('authorization') !== `Bearer ${process.env.HELPSTACK_SECRET}`) {
return new Response('unauthorized', { status: 401 });
}
const job = await req.json();
// Validate `input` before trusting any of it.
const layout = job.input?.layout;
if (layout !== 'wide' && layout !== 'narrow') {
// Refusing here is fine: a non-2xx means the job never started, and a human
// picks the conversation up right away.
return new Response('unsupported layout', { status: 400 });
}
// Acknowledge FIRST, work afterwards.
queueMicrotask(async () => {
let ok = true;
let summary = `Layout set to ${layout}.`;
try {
await applyLayout(job.input.pageId, layout);
} catch (err) {
ok = false;
summary = `Could not apply the layout: ${(err as Error).message}`;
}
await fetch(job.callbackUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: job.callbackToken, ok, summary }),
});
});
return new Response(null, { status: 202 });
}
In production, put the work on a real queue rather than a microtask, so a process restart does not lose it. The deadline is your safety net, not your scheduler.
7. Setting the tool up#
- Ask us to enable the
deferredToolsflag for your organization. - In Settings → Agent Tools, add a tool of type Async job, with:
- a name and description the AI reads to decide when to call it,
- a
parametersSchemadescribinginput, - your URL and headers,
deadlineSecondsandonDeadline.
- Test with a tool that changes nothing before pointing it at anything real.
Current status. The transport is live and the tool type can be created in Settings → Agent Tools once the
deferredToolsflag is on for your organization. Ask us to enable the flag; everything after that is self-serve.
Where this differs from the email transport#
HelpStack has a second async transport, EMAIL_INQUIRY, where the AI emails a
person (a carrier, a supplier) and waits hours for a reply. It looks similar and
is not:
EMAIL_INQUIRY | CALLBACK_JOB | |
|---|---|---|
| Counterparty | A person | A system |
| Waits | Hours or days | Seconds to an hour |
| Is the reply an answer? | Judged by a classifier | Reported by you as ok |
| Chasing | Sends a reminder | No retries |
If you are asking a human something, you want the email transport, not this one.