TextBubbles supports WhatsApp as a first-class send/receive channel alongside iMessage and SMS. WhatsApp uses the same phone number as your existing iMessage/SMS line — one paired WhatsApp account per number.
Capabilities
| Feature | Outbound | Inbound |
|---|---|---|
| Plain text | ✅ | ✅ |
| Image (≤16 MB) | ✅ | ✅ |
| Video (≤64 MB) | ✅ | ✅ |
Voice notes (PTT) via attachments[].isAudioMessage: true | ✅ | ✅ |
| Documents (≤100 MB) | ✅ | ✅ |
| Reactions | ✅ (POST /v1/messages/:id/reactions) | ✅ via message.reaction webhook |
Replies (quoted) via replyTo | ✅ | ✅ |
| Mentions in groups | ✅ | ✅ |
Send to existing groups via to: "...@g.us" | ✅ | ✅ |
| Typing indicators | — | ✅ via typing.indicator webhook |
Pairing a number
Pairing links a WhatsApp account to one of your TextBubbles numbers. The phone number on the WhatsApp account must match the TextBubbles number — mismatched scans are rejected automatically.
You can pair from the textbubbles UI (Settings → WhatsApp) or directly from the API.
API reference
Every endpoint is scoped to numbers your API key owns; requests for a number that isn’t linked to your customer return 403 NUMBER_NOT_OWNED. Phone numbers in the path accept E.164 with or without the leading + (+16282895642 and 16282895642 are both valid).
List your numbers with current WA status
curl -H "Authorization: Bearer $TB_KEY" https://api.textbubbles.com/v1/whatsappReturns an array like:
{
"success": true,
"data": [
{
"phoneNumber": "+16282895642",
"whatsapp": {
"status": "connected",
"jid": "16282895642:2@s.whatsapp.net",
"lastConnectedAt": "2026-04-20T20:39:55.000Z",
"lastDisconnectedAt": null,
"disconnectReason": null
}
}
]
}whatsapp.status is one of not_enabled, disconnected, connecting, qr_pending, connected.
Start pairing (QR flow)
NUM="+16282895642"
# 1. Create the session and start QR flow
curl -X POST -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/enable"
# 2. Poll for the raw QR string (render with any QR library)
curl -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/qr"
# 3. On the phone for this number: WhatsApp → Settings → Linked Devices → Link a Device → scan
# 4. Confirm the pair
curl -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/status"Rather than polling /status, subscribe to the whatsapp.status Realtime event (see Webhooks & Events) for a push update on every transition.
Pair-by-code (no QR display)
curl -X POST -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/pairing-code"Returns an 8-character code. On the phone, choose “Link with phone number instead” during the Link a Device flow and type the code. By default the pairing code is issued for the owned number; pass a different phoneNumber in the body to issue for a different device (rare).
Disconnect / remove
# Stop the live session but keep credentials (quick re-connect later)
curl -X POST -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/disconnect"
# Permanently remove the session — credentials are deleted, next pair requires a fresh QR
curl -X DELETE -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM"Check whether a recipient is on WhatsApp
curl -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/check?phone=+14155551234"Returns { exists: boolean, jid?: string }. Useful to confirm reachability before sending with routing.preference: ["whatsapp"] and fallback: false.
Signing up for WhatsApp on a TextBubbles number
If this number doesn’t yet have a WhatsApp account, you’ll first register one before you can link it:
- Install WhatsApp on a phone (any iOS / Android device you can keep paired — this is the device that becomes the account’s home).
- In WhatsApp, enter the TextBubbles number when prompted.
- WhatsApp sends a verification SMS to that number — which arrives in your TextBubbles inbox.
- Retrieve the code (most easily via the endpoint below), type it into WhatsApp, and the account is created.
- Once the account exists, follow the pairing flow above to link TextBubbles as a device.
Surfacing the verification SMS code
curl -H "Authorization: Bearer $TB_KEY" \
"https://api.textbubbles.com/v1/whatsapp/numbers/$NUM/signup-codes"Scans the last 15 minutes of inbound messages to this number for anything matching WhatsApp’s verification SMS format and returns the extracted codes:
{
"success": true,
"data": {
"windowMinutes": 15,
"codes": [
{
"code": "123456",
"messageId": "msg_xyz",
"from": "+15555550000",
"channel": "sms",
"receivedAt": "2026-04-20T21:00:00.000Z"
}
]
}
}Sending
See Send Messages for the full request shape. The routing.preference field decides which channel to try first; to is an E.164 phone number for individuals or a ...@g.us JID for existing groups.
Reactions
POST /v1/messages/:id/reactions works the same as for iMessage; the same type values apply on WhatsApp:
type | WhatsApp emoji |
|---|---|
love | ❤️ |
like | 👍 |
dislike | 👎 |
laugh | 😂 |
emphasize | ‼️ |
question | ❓ |
Any other string (e.g. 🔥) | passed through verbatim |
Negative form (e.g. -love) | removes the reaction |
curl -X POST -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"type":"love"}' \
https://api.textbubbles.com/v1/messages/msg_xxx/reactionsInbound
Inbound WhatsApp messages, reactions, typing indicators, and delivery/read updates flow through your registered webhook the same way iMessage events do. Each event includes "channel": "whatsapp" and a customerId.
Inbound media (photos, videos, voice notes, documents, stickers) is surfaced as attachments[] with a pre-signed downloadUrl — the same shape iMessage attachments use. Fetch the URL directly (no API key required, 1-hour TTL). See Inbound Message Fields for the full attachment shape.
Limitations
- One WhatsApp account per TextBubbles number. Pairing uses the WhatsApp “Linked Devices” slot; revoking the link from the customer’s phone disconnects the session and requires re-pairing.
- Group operations. Sending into existing groups (and replying / mentioning within them) is supported; creating groups or managing membership through the API is not.
- iMessage-specific features (
effect,messageType: "carousel") are silently dropped on a WhatsApp-only routing.
Errors
Send-path errors surface on the message as errorCode (pull via GET /v1/messages/:id) and on message.failed webhook events.
| Error code | Meaning |
|---|---|
NO_CHANNEL_AVAILABLE | None of the channels in routing.preference are available for this recipient — e.g. {"preference":["whatsapp"], "fallback":false} to a number not on WhatsApp. Include "imessage" / "sms" in the preference list (with fallback: true) if you want graceful degradation. |
WHATSAPP_NOT_CONNECTED | No paired WhatsApp session for this number. Pair via the admin panel. |
WHATSAPP_SEND_FAILED | The WhatsApp send failed for an unexpected reason (transient upstream error, attachment URL unfetchable, etc.). Safe to retry. |
WHATSAPP_MEDIA_TOO_LARGE | Attachment exceeds WhatsApp’s size limits (16 MB image / 64 MB video / 100 MB document). |
WHATSAPP_REACTION_FAILED | The reaction could not be delivered. |
MISSING_WHATSAPP_JID | Returned by POST /v1/messages/:id/reactions when the target message has no stored WhatsApp JID — should be rare; contact support if you see it on a message your customer sent through the API. |
Session-level states (not send errors)
The following are session disconnect reasons surfaced on the WhatsApp instance’s status (via GET /v1/admin/instances/:id/whatsapp/status), not on individual sends. Inspect disconnectReason:
| Value | Meaning |
|---|---|
phone_number_mismatch | The scanned WhatsApp account’s number doesn’t match the TextBubbles number this instance is registered to. The session was logged out automatically; re-pair with an account on the correct number. |
auth_revoked | The customer removed the textbubbles link from their phone’s WhatsApp → Linked Devices. Re-enable to start a fresh QR flow. |
close_<code> | Transient close — the service will attempt to reconnect automatically. |