Home/ Developers/Partner API
Give every customer their own WhatsApp number
For software vendors (ISVs) — school ERPs, clinic or salon software, e-commerce platforms — that want each of their customers to have a WhatsApp number of their own. You create a client workspace, send the customer through a hosted connect page, deploy templates and send messages on their behalf.
The Partner API is machine-to-machine: authenticate with
Authorization: Bearer bpp_<prefix>_<secret>, create a client with
POST /api/v1/partner-api/clients/, and send the customer to a one-time connect
link. Meta bills each client's WhatsApp account directly; you pay BotPulsar per your
partner statement. Partner access is enabled by BotPulsar —
contact us to become a partner.
You probably want the Simple send API
If one business runs its own BotPulsar workspace and just wants an app to send from it, use a normal API key with the Simple send API. The Partner API is for vendors who manage many customer workspaces.
Partner keys
Authorization: Bearer bpp_your_prefix_your_secret
The key acts as the partner's owner or admin who owns it. It stops working if that person stops being an owner or admin, the key is revoked or expires, or the request comes from an IP outside the key's allow-list (when one is set). Rate limit: 20,000 requests per hour. The secret is shown once, when BotPulsar issues it.
Under /api/v1/partner-api/
| Method | Path | Purpose |
|---|---|---|
| GET / PUT | settings/ | Allowed redirect origins, webhook URL and display name. A new webhook URL is applied to every client. |
| POST | clients/ | {external_ref, name, owner_email} — create a client workspace. Idempotent on external_ref: 201 first time (with webhook_secret, shown once), 200 after. |
| GET | clients/?external_ref= | Look a client up by your own id. |
| GET | clients/{id}/ | Status plus channels[] (display_phone_number, quality_rating, can_send, …). |
| POST | clients/{id}/webhook-secret/ | Read the client's signing secret again. |
| POST | clients/{id}/onboarding-links/ | {redirect_url, state?, coexistence?} → {url, expires_at}. One-time link, valid 60 minutes. |
| GET | clients/{id}/templates/ | Every template on the client's WhatsApp account with its status. |
| POST | clients/{id}/templates/deploy-pack/ | {pack} — create and submit a template pack on the client's account. Re-run safe. |
| POST | clients/{id}/messages/ | {to, template:{name, language, params[]}, idempotency_key} or {to, text, idempotency_key}. |
| GET | clients/{id}/messages/{message_id}/ | Message status; inbound also returns from and text. |
Errors use the standard envelope. Send errors you should handle:
template_not_found, template_not_approved,
no_connected_number, invalid_phone,
whatsapp_template_required, contact_suppressed,
billing_limit_reached.
The connect flow
-
Create a link
POST clients/{id}/onboarding-links/with aredirect_urlon an origin you allow-listed in settings. Send the customer's browser to the returnedurl— it is one-time and valid for 60 minutes. -
The customer connects a number
The hosted page runs Meta Embedded Signup — a new number with a 6-digit PIN, or coexistence with an existing WhatsApp Business app number. Opening the page does nothing; signup starts on the button click, so chat link previews cannot use up the link.
-
They land back on your site
We register the number, activate sending, and redirect to
redirect_url?status=success&client_id=…&external_ref=…&state=….statuscan also bependingorcancelled. -
Trust the webhook, not the redirect
partner.channel.connectedis the source of truth. Then calltemplates/deploy-pack/and start sending.
One subscription per client
Every client workspace posts to your webhook_url with the same signing scheme as
the Webhooks page: headers
X-Botpulsar-Delivery, X-Botpulsar-Event,
X-Botpulsar-Timestamp, X-Botpulsar-Signature: v1=<hex>, where the
hex is HMAC-SHA256(webhook_secret, "<timestamp>.<raw body>"). Each client has
its own webhook_secret. Five retries, then dead-letter; deliveries can be replayed.
Events: partner.channel.connected, partner.channel.updated,
partner.template.status_changed, message.delivery_status.changed,
message.outbound.failed, message.received (ids only — read the text
with the message endpoint).