Skip to content

Tracking Progress

Monitor broadcast delivery in real-time

View .md

Track your broadcast's delivery progress with instant updates on sent, delivered, and failed messages.

Get Progress

const progress = await zavu.broadcasts.progress(broadcastId); console.log(`Status: ${progress.status}`); console.log(`Progress: ${progress.percentComplete}%`); console.log(`Delivered: ${progress.delivered}/${progress.total}`); console.log(`Failed: ${progress.failed}`);

Response

JSON
{ "broadcastId": "brd_abc123", "status": "sending", "total": 5000, "pending": 2500, "sending": 100, "sent": 350, "delivered": 2000, "failed": 50, "skipped": 0, "percentComplete": 48.0, "estimatedCost": 75.00, "reservedAmount": 75.00, "actualCost": 35.25, "startedAt": "2024-01-15T10:30:00.000Z", "estimatedCompletionAt": "2024-01-15T10:45:00.000Z" }

Progress Fields

FieldTypeDescription
broadcastIdstringBroadcast identifier
statusstringCurrent broadcast status
totalnumberTotal contacts in broadcast
pendingnumberNot yet queued for sending
sendingnumberCurrently being sent
sentnumberAccepted by the provider, delivery not confirmed yet
deliverednumberDelivery to the device confirmed
failednumberFailed to deliver
skippednumberSkipped (broadcast cancelled)
percentCompletenumberPercentage complete (0-100)
estimatedCostnumberEstimated total cost (USD)
reservedAmountnumberAmount reserved from balance (USD)
actualCostnumberActual cost so far (USD)
startedAtstringWhen sending started
estimatedCompletionAtstringEstimated completion time

Polling for Updates

Poll the progress endpoint at regular intervals:

TypeScript
async function waitForCompletion(broadcastId: string) { const POLL_INTERVAL = 5000; // 5 seconds while (true) { const progress = await zavu.broadcasts.progress(broadcastId); console.log(`[${new Date().toISOString()}] ${progress.percentComplete}% complete`); console.log(` Delivered: ${progress.delivered}`); console.log(` Failed: ${progress.failed}`); console.log(` Pending: ${progress.pending}`); if (progress.status === "completed" || progress.status === "cancelled") { return progress; } await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } } const finalProgress = await waitForCompletion(broadcastId); console.log(`Broadcast ${finalProgress.status}!`);

Status Transitions

Code
draft → scheduled → sending → completed ↓ ↓ cancelled cancelled
StatuspercentCompleteDescription
draft0%Still adding contacts
scheduled0%Waiting for scheduled time
sending0-99%Messages being delivered
completed100%All messages processed
cancelledvariesStopped before completion

Viewing Individual Contacts

Each recipient has one of these statuses:

StatusMeaning
pending, queued, sendingNot handed to the provider yet
sentThe provider accepted the message. Delivery is not confirmed yet
deliveredThe channel confirmed delivery to the device. A WhatsApp read receipt also counts
failedNot delivered. A sent or delivered recipient moves here if the provider reports a failure later
skippedNot sent: the recipient opted out of the channel, or the broadcast was cancelled first

Channels that never report delivery leave their recipients at sent. Completion counts sent recipients as done, so a completed broadcast can still have sent recipients that move to delivered or failed afterwards.

Get detailed status for each contact:

TypeScript
// Get failed contacts const failed = await zavu.broadcasts.contacts.list(broadcastId, { status: "failed", limit: 100, }); for (const contact of failed.items) { console.log(`${contact.recipient}: ${contact.errorMessage}`); }

Contact Status Response

JSON
{ "items": [ { "id": "bc_abc123", "recipient": "+14155551234", "status": "failed", "errorCode": "30003", "errorMessage": "Unreachable destination", "processedAt": "2024-01-15T10:35:00.000Z" } ], "nextCursor": null }

Broadcast Analytics

After completion, get final statistics:

TypeScript
const broadcast = await zavu.broadcasts.retrieve(broadcastId); console.log("=== Broadcast Report ==="); console.log(`Name: ${broadcast.name}`); console.log(`Status: ${broadcast.status}`); console.log(`Total: ${broadcast.totalContacts}`); console.log(`Delivered: ${broadcast.deliveredCount}`); console.log(`Failed: ${broadcast.failedCount}`); console.log(`Delivery Rate: ${(broadcast.deliveredCount / broadcast.totalContacts * 100).toFixed(1)}%`); console.log(`Duration: ${calculateDuration(broadcast.startedAt, broadcast.status_changedAt)}`); console.log(`Actual Cost: $${broadcast.actualCost}`);

Error Analysis

Common failure reasons:

Error CodeDescriptionRecommendation
30003Unreachable destinationInvalid or disconnected number
30004Message blockedContent filtered by carrier
30005Unknown destinationNumber doesn't exist
30006Landline destinationCannot SMS landlines
30007Carrier violationMessage rejected by carrier
rate_limitedToo many messagesTemporary, will retry
insufficient_balanceOut of creditsTop up balance

Export Failed Contacts

TypeScript
async function exportFailedContacts(broadcastId: string) { const failed: string[] = []; let cursor: string | undefined; do { const result = await zavu.broadcasts.contacts.list(broadcastId, { status: "failed", limit: 100, cursor, }); for (const contact of result.items) { failed.push(`${contact.recipient},${contact.errorCode},${contact.errorMessage}`); } cursor = result.nextCursor ?? undefined; } while (cursor); // Write to CSV const csv = ["recipient,error_code,error_message", ...failed].join("\n"); fs.writeFileSync("failed_contacts.csv", csv); console.log(`Exported ${failed.length} failed contacts`); }

Real-time Updates with Webhooks

For real-time updates without polling, configure webhooks:

TypeScript
// In your webhook handler app.post("/webhooks/zavu", (req, res) => { const event = req.body; if (event.type === "broadcast.status_changed") { console.log(`Broadcast ${event.data.broadcastId} completed!`); console.log(`Delivered: ${event.data.deliveredCount}`); console.log(`Failed: ${event.data.failedCount}`); } res.status(200).send("OK"); });

Broadcast Events

EventDescription
broadcast.startedBroadcast started sending
broadcast.status_changedAll messages processed
broadcast.cancelledBroadcast was cancelled

Best Practices

  1. Use appropriate polling intervals

    5 seconds for small broadcasts, 30 seconds for large ones

  2. Handle partial failures

    Some contacts may fail while others succeed - check both counts

  3. Export and analyze failures

    Review failed contacts to improve your contact list quality

  4. Set up webhooks for production

    Avoid continuous polling by using webhook notifications

Complete Monitoring Example

TypeScript
import Zavudev from '@zavudev/sdk'; const zavu = new Zavudev({ apiKey: process.env['ZAVUDEV_API_KEY'], // This is the default and can be omitted }); async function monitorBroadcast(broadcastId: string) { console.log(`Monitoring broadcast: ${broadcastId}\n`); let lastDelivered = 0; let lastFailed = 0; while (true) { const progress = await zavu.broadcasts.progress(broadcastId); // Calculate rate const newDelivered = progress.delivered - lastDelivered; const newFailed = progress.failed - lastFailed; console.log(`[${progress.percentComplete.toFixed(1)}%] ` + `Delivered: ${progress.delivered} (+${newDelivered}) | ` + `Failed: ${progress.failed} (+${newFailed}) | ` + `Pending: ${progress.pending}`); lastDelivered = progress.delivered; lastFailed = progress.failed; if (progress.status === "completed") { console.log("\n=== Broadcast Complete ==="); console.log(`Total: ${progress.total}`); console.log(`Delivered: ${progress.delivered} (${(progress.delivered / progress.total * 100).toFixed(1)}%)`); console.log(`Failed: ${progress.failed} (${(progress.failed / progress.total * 100).toFixed(1)}%)`); break; } if (progress.status === "cancelled") { console.log("\n=== Broadcast Cancelled ==="); console.log(`Skipped: ${progress.skipped} contacts`); break; } await new Promise(r => setTimeout(r, 5000)); } } monitorBroadcast("brd_abc123");