v1 keeps working
v1 is frozen, not retired. Its endpoints, payloads and webhooks keep working exactly as they do today. There is no deadline to move. You can migrate one call at a time. Both versions take the same API key and share the same data: a message, conversation, contact, campaign or agent created through one is read through the other with the same ID. Every entry ofGET /v2/channels carries v1SenderId, the sender to use as Zavu-Sender on
the v1 calls you have not moved yet.
What maps to what
A campaign is a v1 broadcast: its
id works as broadcastId on
/v1/broadcasts/{broadcastId}.
Sending: from a header to an address
202 carries message and routing. message.from and message.to name
addresses; there is no senderId.
To reply in a thread, pass conversationId and drop both from and to. See
Replying in a conversation.
Webhooks
Your v1 sender webhooks keep receiving events after you create the v2 webhook. Each event then goes to both, as separate deliveries with different event ids.PUT /v2/webhookwith your new URL and events.- Check that your receiver verifies and handles the v2 payload.
from,toandtypesit at the top level;datais what v1 sends, withoutsenderId. PUT /v2/webhookagain with"disableSenderWebhooks": true. That switches the v1 sender webhooks off and keeps their configuration, so each event arrives once.
/v1/invitations/webhook) is separate and is not
switched off. Details in Webhook.
What behaves differently in v2
Same engine, same limits and prices. These are the places where a v2 call answers differently from the same v1 call.Sending
- Only active connections are picked. Without
channel, a connection that is switched off is skipped. If nothing active can reach the recipient, the answer is409 no_channel. Naming a switched-off transport inchannelis403 channel_inactive. - One answer for “nothing can send”. No channel, a
channelthat is not connected, an emailtowithout email, a WhatsApp-onlymessageTypewithout WhatsApp: all409 no_channel, withdetails.connectlisting what to connect and the request for each. v1 answers several of these with400. - Contradictions are refused. An email
towith a channel other thanemail,channel: "email"with a phone number, or a WhatsApp-onlymessageTypewith anotherchannelis400, naming both fields. - Bare digits are not guessed. A
toof 8 to 15 digits without+is a chat id. When nothing in the request can reach a chat id, v2 refuses it withdetails.suggestionholding the number with its+. A number written with spaces, dashes or parentheses after its+is read as E.164. - Fallback checks first. Before a failed WhatsApp message falls back to SMS,
v2 checks that the connection it would fall back to is switched on and, for a
+1 number, that the project has an approved 10DLC campaign. Otherwise the
message stays
failed, witherrorCodeCHANNEL_INACTIVEorTEN_DLC_REQUIRED. - Replies stay in their thread. With
conversationId,tocan be omitted, and atothat is not the thread’s contact is400. The202carriesmessage.conversationIdwhen the thread already exists. - 10DLC refusals say how to fix them.
403 ten_dlc_requiredcarries the registration as API calls, in order, and the channels that reach the same person without 10DLC.
Messages
- Amounts are USD rounded to 6 decimals.
chargedsays what an SMS or one-way SMS took from your balance.costTotalis not that number for SMS.errorCodeis a Zavu code. A delivery provider’s own code is translated where its meaning is known and kept inerrorDetails.providerCode.attemptedChannelslists the transports tried before a fallback, andriskHoldsays whether the message is waiting for risk review.
Contacts
- An identifier another contact already holds on the same channel is
400 duplicate_identifier, withdetails.conflictsnaming that contact. v1 refuses it too, as a plain400 invalid_requestwith nodetails. - A phone identifier (
sms,whatsapp,voice) must be E.164; WhatsApp also takes a BSUID. - Deleting a contact that is already gone is
404, as on v1.
Campaigns
- Creating a campaign checks its channel: a
fromwithout that channel is409 no_channel, not a campaign that fails at send. - Adding recipients: a malformed recipient, or bare digits on a phone campaign,
refuses the whole request with
400and the path of the recipient. A well-formed recipient the campaign cannot reach is skipped and reported ininvalid, withcodeunreachable_by_channelorten_dlc_required. - Acting on a campaign in the wrong state (cancel a finished one, add recipients
to or delete one that is not a draft) is
409 invalid_status. v1 answers400.
Channels, agents, domains
- A Telegram token Telegram rejects is
400 invalid_bot_token. - Connecting an agent to one-way SMS is
409 channel_cannot_receive: one-way SMS receives nothing, so the agent would never answer. POST /v2/agentslists every missing required field, not only the first.POST /v2/email-domainsvalidatesdomainbefore anything is created, and adding a domain the project already has returns it with200. A domain whose DKIM records could not be generated comes backverifiable: falsewithsetupError.- Verifying an email domain answers
404only for a domain that is not yours, and503 verification_unavailablewhen the DNS check could not run.
Errors and webhooks
- Every error is flat, and every
400 invalid_requestcarriesdetails.errorswith field paths. A body that is not JSON is400 invalid_json, as on v1. - A known path with the wrong method is
405, not404, on every v2 path except/v2/numbersand/v2/functions. - The v2 webhook cannot move straight from
v2signatures back tov1; go throughv1+v2, as in the other direction.
What stays on v1
These have no v2 endpoint. Call them on v1 with the same API key, usingv1SenderId from GET /v2/channels where they take a sender.
