Skip to main content

Polling vs Webhooks

Two strategies for knowing when a bill has been paid.

Polling

Poll the GET /bills/{id} endpoint at regular intervals until the status changes.

Pros: Simple to implement, no server configuration needed, works everywhere.

Cons: Adds API load, slight delay between payment and detection.

Basic polling

async function pollPayment(api, billId) {
const interval = 5000; // 5 seconds
const timeout = 300000; // 5 minutes

const start = Date.now();
while (Date.now() - start < timeout) {
const bill = await api.get(billId);
if (bill.status.name === 'FULLY_PAID') {
return bill;
}
await new Promise(r => setTimeout(r, interval));
}
throw new Error('Payment timeout');
}

Exponential backoff

For production, increase the interval over time to reduce API load:

async function pollWithBackoff(api, billId) {
let interval = 3000; // Start at 3s
const maxInterval = 30000; // Cap at 30s
const timeout = 600000; // 10 minutes

const start = Date.now();
while (Date.now() - start < timeout) {
const bill = await api.get(billId);
if (bill.status.name === 'FULLY_PAID') return bill;
if (bill.status.name === 'PARTIALLY_PAID') {
interval = 3000; // Reset to fast polling on partial payment
}
await new Promise(r => setTimeout(r, interval));
interval = Math.min(interval * 1.5, maxInterval);
}
throw new Error('Payment timeout');
}

Webhooks

Cashless sends a POST request to your server when a bill is paid. See Webhooks for setup.

Pros: Real-time, no polling overhead, scales well.

Cons: Requires a publicly accessible server, needs retry handling.

Example

app.post('/webhooks/cashless', async (req, res) => {
const { bill } = req.body;

// Always verify via API
const verified = await api.get(bill.id);
if (verified.status.name === 'FULLY_PAID') {
await fulfillOrder(bill.reference);
}

res.sendStatus(200);
});

For maximum reliability, use webhooks as the primary notification and polling as a fallback:

// On bill creation, start a background poll
const bill = await api.issue(params);
startBackgroundPoll(bill.id, bill.reference);

// Webhook handler (fast path)
app.post('/webhooks/cashless', async (req, res) => {
const { bill } = req.body;
const verified = await api.get(bill.id);
if (verified.status.name === 'FULLY_PAID') {
stopBackgroundPoll(bill.id);
await fulfillOrder(bill.reference);
}
res.sendStatus(200);
});

This ensures payment is detected even if the webhook delivery fails.