πŸ’¬ SalesByte

Messages

Send WhatsApp messages via the API.

Send a message

POST /api/v1/messages

Send a text, image, video, or document to any WhatsApp number or group.

Request body

{
  "to": "+919876543210",
  "text": "Hello! This is a test message."
}

Parameters

FieldTypeRequiredDescription
tostringβœ…Phone number with country code, or a group JID
textstringβœ… (unless sending media)The message text
imageUrlstring❌Public URL of an image to send
videoUrlstring❌Public URL of a video to send
documentUrlstring❌Public URL of a document to send
fileNamestring❌ (required with documentUrl)File name shown to recipient
connection_idstring❌Which of your numbers to send from (alias: from) β€” a Personal or Business number's id from GET /api/v1/connections. Defaults to your API key's Personal number, or the workspace's only Personal number.
channelstring❌Set to "business" to send from your Business WhatsApp number without naming it (only when you have exactly one).

Sending from a Business WhatsApp number

Pass the Business number's connection_id (or "channel": "business"). Business numbers support everything above plus these message types:

FieldTypeDescription
buttonsarrayUp to 3 quick-reply buttons under text. Each: { "id": "confirm", "title": "Confirm" } β€” title max 20 characters; id comes back to you when tapped (defaults to btn_1, btn_2…).
listobjectA menu under text: { "button_text": "View options", "sections": [{ "title": "...", "rows": [{ "id": "...", "title": "...", "description": "..." }] }] }. Max 10 rows in total; row title max 24 characters, description max 72. sections[].title is required when there is more than one section.
cta_urlobjectOne button under text that opens a link: { "display_text": "Track order", "url": "https://..." } β€” label max 20 characters.
locationobjectA map pin: { "latitude": 28.6139, "longitude": 77.209, "name": "Our showroom", "address": "..." }.
audioUrlstringPublic URL of an audio file or voice note (OGG, MP3, AAC, M4A, AMR). Sent without a caption.
headerstringOptional bold heading on a buttons, list or link-button message (max 60 characters).
footerstringOptional small text under a buttons, list or link-button message (max 60 characters).

These are Business-only β€” sending them from a Personal number returns 400.

The 24-hour rule. WhatsApp only lets a Business number send free-form messages β€” text, media, buttons, lists, link buttons and locations β€” within 24 hours of the customer's last message to that number. Outside that window the API returns 422 outside_window; send an approved template instead with POST /api/v1/templates/:id/send, which works at any time.

Business messages are charged to your wallet the same way as messages sent from the Inbox. Group sends are Personal-only.

When a customer taps a button or picks a list row, your message.received webhook carries the id you set β€” see interactive_payload there.

Response

{
  "success": true,
  "message_id": "3EB0A1B2C3D4E5"
}

A Business send also returns "channel": "business" and the connection_id it was sent from.

Keep message_id β€” the message.delivered and message.read webhooks for this send carry the same value in wa_message_id, so that's how you match a status back to your message. Treat it as an opaque string: depending on the channel it may not look like a 3EB0… WhatsApp id. It can occasionally be null; the message was still sent, but its status can't be matched.

Examples

Send a text message:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "text": "Hi Rahul, your order is ready for pickup!"
  }'

Send an image:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "imageUrl": "https://example.com/invoice.jpg",
    "text": "Here is your invoice."
  }'

Business number β€” message with buttons:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "connection_id": "YOUR_BUSINESS_CONNECTION_ID",
    "text": "Your appointment is tomorrow at 11 AM. Will you make it?",
    "buttons": [
      { "id": "confirm", "title": "Confirm" },
      { "id": "reschedule", "title": "Reschedule" }
    ]
  }'

Business number β€” list menu:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "channel": "business",
    "text": "Pick a time slot for your demo.",
    "list": {
      "button_text": "View slots",
      "sections": [{ "rows": [
        { "id": "10am", "title": "10:00 AM", "description": "Tomorrow" },
        { "id": "3pm",  "title": "3:00 PM",  "description": "Tomorrow" }
      ]}]
    }
  }'

Business number β€” link button:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "channel": "business",
    "text": "Your order has shipped!",
    "cta_url": { "display_text": "Track order", "url": "https://example.com/track/1234" }
  }'

Send to a group:

curl -X POST https://app.salesbyte.in/api/v1/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "120363XXXXXXXXX@g.us",
    "text": "Team meeting at 3 PM today."
  }'

JavaScript example

const response = await fetch('https://app.salesbyte.in/api/v1/messages', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: '+919876543210',
    text: 'Hello from the API!',
  }),
});
 
const data = await response.json();
console.log(data.message_id);

Errors

StatuserrorMeaning
400bad_requestA field is missing or invalid β€” the message says which.
400ambiguous_connectionYou have more than one number of that kind; pass connection_id. The response lists the choices.
402insufficient_balanceNot enough wallet balance for a Business message.
422outside_windowBusiness number, but the customer hasn't messaged it in the last 24 hours. Use a template.
422no_connection / no_sessionNo active number to send from.
429rate_limited / daily_limit_reachedToo many requests, or the Business number's daily limit is reached.
502send_failedWhatsApp rejected the message; the message explains why.
404flow_not_foundNo Flow with that id or name. See GET /api/v1/flows.
422flow_not_published / flow_wrong_numberThe Flow isn't published yet, or it belongs to a different Business WhatsApp account than the number you're sending from.

Send a WhatsApp Flow (a form inside WhatsApp)

A Flow is a form the customer fills in without leaving WhatsApp: a booking, a lead form, a document checklist. Create and publish it under WhatsApp Flows in SalesByte, then send it from a Business number to a customer who has messaged you in the last 24 hours.

First list your Flows:

GET /api/v1/flows
{
  "success": true,
  "flows": [
    {
      "id": "627443a3-a4e3-4a15-b052-5c7427bdbdf8",
      "name": "Lead Capture",
      "status": "PUBLISHED",
      "default_body": "Quick question for you:",
      "default_cta": "Get started",
      "connection_ids": ["7b7af436-fa81-4219-9f5b-dd0c3ff3a2a7"]
    }
  ]
}

Only PUBLISHED Flows can be sent, and only from a number listed in connection_ids.

Then send it with POST /api/v1/messages:

{
  "to": "+919876543210",
  "connection_id": "7b7af436-fa81-4219-9f5b-dd0c3ff3a2a7",
  "text": "Please share your documents for the loan application.",
  "flow": { "id": "Lead Capture", "cta": "Open form" }
}
FieldRequiredDescription
flow.idYesThe Flow's id from GET /api/v1/flows, or its name.
flow.ctaNoThe button label, up to 20 characters. Defaults to the Flow's own.
flow.dataNoValues to pre-fill the Flow's first screen.
text, header, footerNoOverride the message shown with the Flow.

When the customer submits the form you get a message.received webhook with "interactive_type": "nfm_reply". The answers are in interactive_payload.response, keyed by the field names in your Flow.

Mark a message read (and show typing)

POST /api/v1/messages/read

Marks a customer's Business WhatsApp message as read, so they see blue ticks. With "typing": true they also see typing… for up to 25 seconds, or until your next message reaches them. Use it while your bot is preparing a reply. Typing is only available together with a read receipt. This call is free.

Request body

{
  "wa_message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgg...",
  "typing": true
}
FieldRequiredDescription
wa_message_idYesThe wa_message_id from the message.received webhook.
typingNotrue to show typing as well. Default false.

Response

{ "success": true, "wa_message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgg...", "typing": true }

Errors

StatuserrorMeaning
400missing_wa_message_id / invalid_typingA field is missing or invalid.
404not_foundNo message with that id in your workspace.
422not_inboundThat message was sent by you, not the customer.
422not_businessRead receipts and typing work on Business WhatsApp numbers only.
422no_connectionThe number the message came in on is no longer connected.
502send_failedWhatsApp rejected the request; the message explains why.

On this page