Configure Voice failover (Termii → Infobip)
Nerve places Voice OTP calls through a two-carrier chain: Termii first, then Infobip. If Termii fails or stalls, Infobip places the call within the same dispatch, so your code sees one event and one delivery receipt regardless of which carrier answered.
Both carriers are Bring-Your-Own: you add your own credentials in the dashboard. This how-to takes about five minutes.
1. Add the Termii credential
- Open the dashboard and go to Providers.
- Add a Termii provider and paste the API key from your Termii account. Voice uses the same Termii credential as SMS — there is no separate voice key.
- Set its priority to
1so it is tried first. - Click Test Connection to verify the credential before real traffic.
Termii carries the primary call. It does not accept a named voice profile, so
voice_id is ignored on this leg — the carrier uses its default voice.
2. Add the Infobip credential
- Add an Infobip provider with your Infobip API key and base URL.
- Set its priority to
2so it is the fallback. - Click Test Connection.
Infobip is the failover leg and the only one of the two that honours
voice_id, so name a TTS voice if you want a specific one.
priority controls failover across all channels. A credential used for both
SMS and Voice keeps the same order. See Provider
credentials for the full model.
3. What failover actually does
Voice is opt-in per event: a call is only eligible when the request supplies
overrides.voice (the SDK's voice.send always does). Eligibility is not the
same as being tried first:
- Voice is attempted first only when the event priority is
CRITICAL/HIGHor the request setsextraParams.voice_primaryto"true"/"1". - For a routine (
NORMAL/LOW) event without that flag, the cost order applies: SMS is tried first, and voice is the failover once the SMS attempt times out or fails.
Once voice is the channel being attempted:
- Each carrier attempt gets a 5-second budget. A carrier that accepts the connection but never answers is treated as failed rather than left to ring.
- The first success wins. The receipt names the carrier that placed the call as
provider, so you never have to guess which leg ran. - If both carriers fail, the event ends
FAILEDwitherror_codeDISPATCH_FAILEDand the carriers that were tried inerror_detail.
You do not implement this retry yourself — Nerve has already tried both
carriers. Retrying a 202-accepted event only risks a duplicate call. See
Failover & circuit breakers.
4. Provider differences to know
| Behaviour | Termii | Infobip |
|---|---|---|
| Role | Primary | Fallback |
voice_id (TTS profile) | Ignored | Honoured |
language | BCP-47 tag | Reduced to the primary subtag (en-US → en) |
| Credential | API key, optional sender id | API key, base URL, optional sender id |
For the full request contract, see the Voice reference.
5. Verify a call
In Test Mode (nerve_sk_test_...) neither carrier is dialled: the worker
records a normal timeline and returns a synthetic success, so you can exercise
the chain without cost. When you are ready for a real call, send a CRITICAL
event (or set extraParams.voice_primary=true) so voice is attempted first
rather than ordered behind SMS, and confirm the receipt:
const receipt = await nerve.events.get(eventId);
console.log(receipt.channel, receipt.provider, receipt.status);
Read channel before provider: provider uses the same Termii/Infobip names
for SMS as for voice, so provider: 'termii' alone does not prove a call was
placed. Only once channel: 'voice' does the carrier tell you about the call —
provider: 'termii' means the primary voice carrier answered, and
provider: 'infobip' means voice failover ran, worth a look at the Termii
credential or its account balance. A receipt with channel: 'sms' is an SMS
dispatch, not a voice call.