Zera Company

Documentation

WhatsApp API through Zera: what you get and what to do with it.

The endpoint and payload reference is Meta own, and it stays that way. This page documents the part that belongs to Zera: how the credential reaches you, what the connection does to your number, which errors show up and what to do about each one.

Whatever was not confirmed in the code is not written here. Where information is missing, this page says so and explains why. Documentation that fills a gap with a guess costs more than documentation that does not exist.

What you get

What is delivered is a Meta Cloud API credential, on your number, inside your own WhatsApp Business account. There are five fields, and the panel returns exactly these.

token string The authorization Meta generated for your company when you connected the number. It goes in the Authorization header of your calls. It is not a Zera key, not a platform token, not a shared secret.
phone_number_id string The identifier of your number on the Cloud API. This is what goes into the message sending URL.
waba_id string The identifier of your WhatsApp Business account on Meta. Use it to query numbers, message templates and subscriptions.
webhook string or null The https address you gave us to receive events. Null means you have not given one yet, and the screen says so instead of hiding it.
expira_em date or null The token expiry, when there is one. Null means Meta returned no deadline, not that the token expired. By default the Embedded Signup token does not expire by time.

Who owns this, in practice

  • The WhatsApp Business account is your company, and so is the token. Zera stores it encrypted in order to operate your number, and ownership stays with you.
  • Only Meta can revoke that token. Zera cannot, and that is why there is no button in the panel to generate a new one. A button that promises what the system cannot deliver is worse than no button.
  • Since the credential cannot be rotated, showing it only once makes no sense either: whoever lost it would be stuck. The rule is the other one: you can see it as many times as you need, and every reveal makes noise.

How to retrieve the credential

The credential lives on the channel screen, inside the panel. It is never sent over WhatsApp or e-mail, and it never travels in a URL. The rules below are not bureaucracy: each one closes a concrete path, and the screen explains every refusal in plain words.

Only on the plan without a shared inbox

On the API plan the credential is the product. On a plan with a shared inbox, ZeraSync uses that same credential, and rotating or leaking it would take down your own service desk. On those plans the screen explains this and shows nothing.

A session younger than ten minutes

Being logged in is not enough. The session cookie lasts thirty days, and the credential outlives it. If your session is older than ten minutes, the screen asks for a fresh link and offers the button that sends it, instead of throwing a dry error.

Three reveals per day, per account

The count includes the attempt in progress and counts attempts, not successes. If you hit the ceiling, the screen says you asked too many times today, and does not pretend the failure was ours.

Every reveal notifies the account owner

An e-mail goes out right away with date, time and origin, and a line appears on the channel Activity tab. If the provider does not accept the e-mail, the credential is not shown. The notice is the lock, not the decoration: with no way to revoke, the only defence is you finding out fast.

An expired token is not delivered

If the token already expired, the screen says so and offers to reconnect the number. Handing over a dead credential would cost you half an hour debugging your own system for something we should have told you.

Connecting your number

The connection happens in Meta own window, in your browser. What changes the path is the situation your number is in today, and each path runs different server side steps.

A brand new number A number with no WhatsApp on it. You need to receive an SMS or a call on it for verification.
A number on regular WhatsApp The account has to be deleted on the handset first, and the local history is lost. That is stated on screen before the click, not after.
A number on WhatsApp Business This is coexistence. The number keeps working on the phone and the history is preserved. It is the most used path, and it has its own list of changes right below.
A number on another API platform There is no button today, and the absence is deliberate. Migration depends on the old platform turning off two step verification, and Zera cannot unlock that alone. This case goes into a queue with a person, with instructions on screen. Promising a button here would be selling something that does not exist.

What changes on your number after coexistence

This list comes from Meta official table on onboarding Business app users, not from someone remembering. It is here because the difference between an informed developer and a support ticket is reading it beforehand.

  • Companion devices linked to the number are unlinked.
  • Broadcast lists are disabled. New ones cannot be created, and existing ones become read only.
  • Groups do not sync. They keep working only in the app.
  • Disappearing messages are disabled in one to one conversations.
  • View once is disabled in the same conversations.
  • Live location is disabled.
  • Voice and video calls stay in the app only.
  • Catalogue, orders and status stay in the app only.
  • Throughput is fixed at twenty messages per second.

In coexistence the number is already registered on the Cloud API, so the registration step is skipped on purpose: calling it anyway would burn one of the ten calls Meta allows per number every 72 hours. And the list of numbers on your account can come back empty for a few minutes after the dialog ends. In that case the connection waits instead of failing. Honesty about the source: that delay is not documented by Meta anywhere, it is a report from people who operate. What Meta documentation does support, and what grounds the decision, is a different thing: the finish event proves the dialog ended, not that every backend step succeeded.

The first message

One endpoint, one key, one minute. The address is Meta own, and the message goes from your server straight there. Zera does not sit in the middle.

curl -X POST https://graph.facebook.com/v25.0/SEU_PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "to": "5545999999999",
    "type": "text",
    "text": { "body": "Hello!" }
  }'

Version v25.0 is the one the connection uses today, and it is valid until 29 July 2028. Always sending the version in the URL is deliberate: a call without a version uses whatever is pinned in the app panel, and when a version expires Meta silently forwards to the next one, changing behaviour with no warning.

The webhook

What is confirmed

  • Zera subscribes its app to your company WhatsApp Business account, and reads the subscription back afterwards to confirm it is really there. Without that subscription, no event from your number leaves Meta.
  • You give an https address on the onboarding form. https is mandatory: Meta refuses http addresses, and finding that out at configuration time throws you back to the end of the queue.
  • The address is optional at purchase. Half of the people who buy the raw credential are still building and have no address to give, and blocking delivery over that would create a human queue for nothing.

What is not documented yet, and why

  • Pointing the events at your address is done today by a person at Zera, as a step in the delivery queue, and not by code that could be cited here.
  • While that is the case, this page does not describe the exact shape of what arrives at your address, nor the headers, nor the signature. There is no implementation of ours to check against, and writing that from memory is how wrong addresses get published to people who will build a receiver on top.
  • The contract that applies is Meta own: the body is the Cloud API payload, in Meta format. Before writing your receiver, confirm with Zera which pointing is in force on your number.

Common errors

These are Meta codes, and they show up both when connecting your number and in your own calls. The decision always comes from the code, never from the wording of the message: Meta itself recommends not relying on the text for logic, because it rewrites the text without notice.

Code What to do
133016 Number registration blew the budget and the number is locked for 72 hours. Do not insist: every extra attempt pushes the deadline further. Wait, and read the time in error_data.details.
133008 and 133009 A wait with a deadline. The time is written in error_data.details, in the response itself. Read it from there. Meta does not publish a fixed number for these two, and a guessed value either locks your number for too long or burns an attempt from the budget.
133005 The number already had two step verification, and the PIN is yours. The way out is turning verification off in WhatsApp Manager and connecting again. Zera cannot unlock that from its side.
190 The token is no longer valid. Retrieve the credential again in the panel, and reconnect the number if the screen asks.
200 Missing permission for what was requested. Check that the token belongs to the right number.
368 Account restricted for a policy violation. Retrying does not unlock anything, and still generates calls on an already flagged account. The way out is resolving the restriction with Meta.
100 Invalid parameter. On reads it is usually a field requested in fields that does not exist in that Graph version, and in that case you lose the whole list over one optional field. On authorization calls it is usually one parameter too many.
4 A ceiling on the whole app, not on your number. Retrying does not help. If it shows up, every client of that app stopped at once.
1 and 2 Temporary unavailability at Meta. Trying again later is the right answer.
17, 341, 80007, 80008, 131000, 133004 and 133015 Zera treats these as retryable: trying again shortly has a chance of working.
3, 10, 131031, 133006 and 133010 Zera treats these as final: insisting does not unlock anything and still spends a call.
  • The last two rows are classified by what Zera code does with them. The exact meaning of each one belongs to Meta and lives in its error code reference. Repeating Meta text here from memory would be inventing.
  • A call that dies mid flight, or a 5xx with no body, is not a refusal from Meta: it is Meta not having answered. Those are different things, and the second one asks for a retry.
  • An unknown code is treated as final, on purpose. Insisting on an error nobody understands, against an endpoint that creates real accounts, is how a limit gets discovered after it has been burned.

The limits that apply

The Embedded Signup code lasts 30 seconds

It is single use, and the exchange happens inside the browser request itself, not in a queue. Queuing it would mean waking up with it already burned.

Number registration: 10 calls per 72 hours

That is Meta ceiling, per number. The eleventh locks the number for three days. Zera stops at 6 on purpose, leaving room for a manual registration without touching Meta limit.

Coexistence: 20 messages per second

It is fixed, and it comes from Meta own table on onboarding Business app users.

High capacity is requested, not bought

Higher throughput is released by Meta, number by number, under its own criteria. Zera prepares the request and follows it. If Meta does not approve, you pay nothing extra.

The token does not expire by time, by default

A null expira_em means Meta returned no deadline. Treating a missing deadline as an expiry would take the credential away from someone whose credential works.

What is not documented yet

This list exists so you do not waste time looking for what is not there. Each item states the reason, because an absence with no reason looks like an oversight.

The shape of the event that arrives at your webhook
For the reason in the webhook section: the pointing is a human step today, and there is no implementation of ours to check against. Confirm with Zera before writing the receiver.
A test environment
There is no Zera sandbox. You test on your own connected number, against Meta Cloud API. If one ever exists, it lands here.
A Zera library or SDK
It does not exist, and it will not. What applies is Meta Cloud API, with Meta payloads. That is why your code keeps working if you ever change provider.
A Zera endpoint for you to call
It does not exist, and that is not a gap: it is the design. Your message goes from your server to graph.facebook.com. Zera connects the number and keeps it connected.

The connection is ours. The API is Meta, and it stays yours.

BRL 147 per number, per month. No lock in, and nobody in the middle of your message.

See the API plans

Something missing on this page? Talk to Zera