Symptom

A charging session stayed stuck in “active” status even after the CDR callback was received. Only affected specific sessions intermittently.

Root cause

The polling function fetched one page of sessions and returned it — never checking for more. VoltNet’s default page limit is 20. When >20 sessions were updated in a 30-second window, any session ranked #21+ was silently truncated. If that was the session’s final status update, it was missed permanently.

// ❌ Assumes all sessions fit in one response
const res = await axios.get(url)
return res.data?.sessions || []

This bug was silent — no errors, worked fine under low volume, only manifested under concurrent load.

Fix: pagination loop

while (hasMorePages) {
    const res = await axios.get(`${url}&offset=${offset}`)
    const sessions = res.data?.sessions || []
    allSessions.push(...sessions)
 
    if (sessions.length < PAGE_SIZE) {
        hasMorePages = false
    } else {
        offset += PAGE_SIZE
    }
 
    if (offset >= 2000) {  // safety limit — prevent infinite loops
        logger.error('Pagination safety limit reached')
        break
    }
}

Lessons

  1. Never assume complete results from a paginated API — if result count equals the limit, there may be more pages
  2. Silent failures are the worst — no errors, fails only under specific load conditions, invisible in testing
  3. Always add a pagination safety limit to prevent infinite loops
  4. Test at page boundaries — test with exactly 20, 21, 40, 41 records to catch truncation bugs

There was also a secondary issue: callbacks were being sent to v0.2.0 instead of v0.3.0, causing 400 errors. Fixed by updating the callback URL config. Callbacks should be primary; polling is the fallback.