Skip to content

Get results by polling

Polling means your system asks Gowajee for new results on a timer, for example every 2 minutes. It is the recommended way to collect results.

Polling (recommended) Webhook
How you get results You call the API on a timer Gowajee POSTs each result to your server
Missed results None, as long as you read before the data is gone. Your data-retention policy clears old data. The calls of a deleted campaign, or of a deleted recipient, stop showing. Possible: if your server is down, delivery stops after 3 tries within seconds.
What you need Outbound HTTPS only. Works behind a firewall. A public HTTPS endpoint
Delay Your polling interval (e.g. 1–2 minutes) Seconds

You can also use both: the webhook for speed, and polling as a safety net.

  1. Check progress with GET /api/campaign/:id/status. It returns the campaign status and how many people are in each state. It is cheap, so use it to decide whether there’s anything new.

  2. Read new results with GET /api/campaign/:id/calls. Calls come most recently updated first, 100 per page. Read page 1, 2, 3… and stop as soon as you reach calls that haven’t changed since your last run.

  3. Save each call with an upsert keyed on call.id (not sessionId, which is null until a call is dialled). A call can show up again when it changes, for example from ANALYZING to COMPLETED. The upsert simply overwrites it.

  4. Stop polling only when both are true: the campaign is COMPLETED, CANCELLED or EXPIRED, and no call is still SCHEDULED, QUEUED, INITIATED, RINGING, IN_PROGRESS or ANALYZING. A cancelled or expired campaign can still have calls that are finishing. Until then, keep reading.

callStatus Finished? What to do
COMPLETED ✅ callResult is ready.
NO_ANSWER, BUSY, FAILED ✅ for this attempt A retry may come later as a new call with a new id.
CANCELLED, EXPIRED ✅ No result will come.
ANALYSIS_FAILED ✅ for now No result yet. It can still become COMPLETED later, so keep the call in your upsert.
SCHEDULED, QUEUED, INITIATED, RINGING, IN_PROGRESS, ANALYZING ⏳ Still going. Read it again next time.

This reads only what changed since the last run, using updatedAt as a marker (a “watermark”). Run it every 1–2 minutes per active campaign. Once the campaign is finished, it reads everything each time, and stops when no call is still going.

/calls lists EXPIRED calls, and calls whose data was deleted by the retention policy, after all the others. So a read that stops at the watermark can skip them. The full read at the end picks them up.

const API = 'https://api.voice-agent.gowajee.ai';
const headers = { 'X-API-Key': process.env.GOWAJEE_API_KEY };
const FINAL_CAMPAIGN = ['COMPLETED', 'CANCELLED', 'EXPIRED'];
const UNFINISHED_CALL = ['SCHEDULED', 'QUEUED', 'INITIATED', 'RINGING', 'IN_PROGRESS', 'ANALYZING'];
async function get(path) {
const res = await fetch(`${API}${path}`, { headers });
if (!res.ok) throw new Error(`${res.status} ${path}: ${await res.text()}`);
return res.json();
}
/**
* Pull every call that changed since `since` (a Date) and save it.
* Returns the new watermark, and how many of the calls it read are still going.
*/
async function syncCampaign(campaignId, since) {
// Re-read a small overlap so a call updated during the last run is not missed.
const cutoff = new Date(since.getTime() - 5 * 60 * 1000);
const next = new Date();
let unfinished = 0;
for (let page = 1; ; page++) {
const { data } = await get(`/api/campaign/${campaignId}/calls?page=${page}`);
for (const call of data.calls) {
if (new Date(call.updatedAt) < cutoff) return { next, unfinished }; // everything older is already saved
await saveCall(call); // your upsert, keyed on call.id
if (UNFINISHED_CALL.includes(call.callStatus)) unfinished++;
}
if (page >= data.totalPages) return { next, unfinished };
}
}
async function pollCampaign(campaignId) {
let since = new Date(0); // first run reads everything
for (;;) {
const { data } = await get(`/api/campaign/${campaignId}/status`);
const finished = FINAL_CAMPAIGN.includes(data.status);
// Once the campaign is finished, read everything, and stop only when no call is still going.
const result = await syncCampaign(campaignId, finished ? new Date(0) : since);
since = result.next;
if (finished && result.unfinished === 0) return;
await new Promise((r) => setTimeout(r, 2 * 60 * 1000)); // every 2 minutes
}
}
async function saveCall(call) {
// Example: upsert into your DB. callDuration is a string like "120.120", so parse it.
const duration = call.callDuration == null ? null : parseFloat(call.callDuration);
const fields = call.callResult?.postCallAnalyticsResult ?? call.callResult?.customizableAttr ?? {};
console.log(call.id, call.callStatus, call.recipient?.tel, duration, fields.promise_to_pay?.value);
}
  • Poll each active campaign every 1–2 minutes. There is no fixed rate limit, but faster than every 30 seconds doesn’t get you results any sooner.
  • Keep the watermark (since) in your database, so a restart continues where it stopped.
  • Want one row per person instead of per call? Read /recipients. It gives each person’s latestCallStatus and callAttempt.
  • Need the recording? Call /record-url for that call when you need it. The link expires within 1 hour.