Skip to main content
This playbook is for applications that already process tasks through a queue or background worker. Use async chat when:
  • The caller should not wait for the final answer
  • Your application has a job queue
  • You want controlled polling and retry behavior
  • You need to process longer-running assistant requests outside the web request cycle
Use sync chat when the caller needs the final answer immediately and the request volume is moderate.

Flow

  1. Web app creates a local job
  2. Worker sends POST /m2m/sites/{id}/chat/async
  3. Worker stores request_id
  4. Worker polls GET /m2m/sites/{id}/chat/requests/{requestId}
  5. Worker stores the completed answer or failure state
  6. Application notifies the user through its own channel

Job payload

Keep the queue payload minimal.
{
  "job_id": "job_48291",
  "session_id": "support_thread_48291",
  "message": "Can you summarize the refund policy?",
  "response_language": "en"
}
Do not put API keys in queue payloads. Workers should read API keys from protected runtime configuration.

Worker example

This example uses placeholder queue functions. Replace them with your queue library.
import { uppzy } from "./uppzy-client.js";
import { getJob, markJobCompleted, markJobFailed, saveRequestId } from "./queue-client.js";

const SITE_ID = process.env.UPPZY_SITE_ID;

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function waitForRequest(requestId, { intervalMs = 2500, timeoutMs = 45000 } = {}) {
  const startedAt = Date.now();

  while (Date.now() - startedAt < timeoutMs) {
    const status = await uppzy(`/m2m/sites/${SITE_ID}/chat/requests/${requestId}`);

    if (status.status === "completed") {
      return status;
    }

    if (status.status === "failed") {
      throw new Error(status.error || "Async chat failed");
    }

    await sleep(intervalMs);
  }

  throw new Error("Async chat polling timed out");
}

export async function processUppzyChatJob(jobId) {
  const job = await getJob(jobId);

  try {
    const request = await uppzy(`/m2m/sites/${SITE_ID}/chat/async`, {
      method: "POST",
      body: {
        session_id: job.session_id,
        message: job.message,
        response_language: job.response_language || "en",
      },
    });

    await saveRequestId(jobId, request.request_id);

    const result = await waitForRequest(request.request_id);

    await markJobCompleted(jobId, {
      session_id: result.session_id,
      request_id: result.request_id,
      answer: result.answer,
    });
  } catch (error) {
    await markJobFailed(jobId, {
      reason: error.message,
      retryable: error.status === 429 || error.status >= 500,
    });
  }
}

Retry policy

Use retry only where it is safe:
  • Retry 429 with backoff
  • Retry 5xx with backoff
  • Do not automatically retry 401 or 403
  • Do not retry forever after polling timeout
  • Keep a maximum retry count in your own queue system

Polling policy

Start with a simple polling policy:
const pollingPolicy = {
  intervalMs: 2500,
  timeoutMs: 45000,
  maxQueueRetries: 3,
};
Increase the timeout only if your user experience and worker capacity can support it.

Rollout checklist

  • Keep API keys out of queue payloads
  • Store request_id with the local job record
  • Make retries bounded and visible
  • Treat 401 and 403 as configuration incidents
  • Track timeout rate after rollout
  • Add feedback collection when users review the final answer