HelpStackDocs

Integrationen

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 typeCALLBACK_JOB
You receiveOne POST to a URL you choose
You reply withOne POST back to https://helpstack.eu/api/deferred-tools/callback
Progress (optional)The same POST, sending progress instead of ok
Auth, us → youWhatever headers you configure on the tool (stored encrypted)
Auth, you → usA single-use token we send you in the job
Timeout5 seconds to 1 hour, you choose
Feature flagdeferredTools (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_SIDE tool. 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"
}
FieldMeaning
loopIdOur id for this job. Useful in your logs; you do not send it back.
toolThe tool name, so one endpoint can serve several tools.
inputThe parameters the AI filled in, matching the tool's parametersSchema.
callbackUrlWhere to report back. Read it from the payload rather than hardcoding it.
callbackTokenSingle use. Your authentication when you call back. Treat it as a secret.
expiresAtAfter 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 }
      }'
FieldRequiredNotes
tokenyesThe callbackToken from the job. Single use.
okfor a completiontrue if the work succeeded, false if it could not be done. Omit it to send a progress report instead (see below).
summarynoOne or two plain sentences, max 2000 chars. This is the only field the AI ever sees.
payloadnoStructured 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 jobThe token stays usable for the completion that follows
Does not reach the AIThe label renders as chat text only, and is never fed to a model
Max length120 characters — it renders on one line
Omitting okIs 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:

onDeadlineWhat happens
ESCALATEA human is put on the conversation. The customer is told nothing automatically.
FAILThe 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:

  1. Verify our auth header on every dispatch (see §2).
  2. Treat input as 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.
  3. 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.
  4. Do not echo the customer's words back in summary unless 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#

  1. Ask us to enable the deferredTools flag for your organization.
  2. 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 parametersSchema describing input,
    • your URL and headers,
    • deadlineSeconds and onDeadline.
  3. 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 deferredTools flag 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_INQUIRYCALLBACK_JOB
CounterpartyA personA system
WaitsHours or daysSeconds to an hour
Is the reply an answer?Judged by a classifierReported by you as ok
ChasingSends a reminderNo retries

If you are asking a human something, you want the email transport, not this one.