Save 44% with yearly billing.View Plans
textbee Logotextbee.dev
Documentation

Receiving SMS

Receive SMS sent to your Android phone through textbee: turn on forwarding, then get each message by webhook or by polling the messages endpoint.

Updated

textbee forwards each SMS that arrives on your registered Android phone to your account. Your server gets the message in one of two ways: a webhook that textbee pushes to your URL, or a poll of the messages endpoint. Turn on Receive SMS in the app first, because nothing is forwarded without it.

How receiving works

  1. A person sends an SMS to the phone number of the SIM in your phone.
  2. The textbee app on the phone reads the message and uploads it to textbee.
  3. textbee stores the message in your history with direction: "received" and status: "received".
  4. textbee sends a MESSAGE_RECEIVED event to each webhook subscription that asks for it.
  5. Your code reads the message from the webhook body or from GET /gateway/messages.

If the phone is offline when the SMS arrives, the app uploads it when the phone reconnects. The message then appears in history and webhooks at that later time. See Late uploads for very late messages.

Turn on receiving

  1. Open the textbee app on the phone.
  2. Go to Settings. In the SMS section, turn on Receive SMS ("Forward incoming SMS to backend").
  3. Grant the SMS permission when Android asks for it. The app cannot read incoming SMS without it.
  4. Send a test SMS to the phone from another phone.
  5. Check the dashboard. The message appears in the message history.

The device record shows the setting too. GET /gateway/devices returns receiveSMSEnabled for each phone. It must be true for received history and webhooks. See Managing devices for the other device fields.

If a test SMS does not appear, open Settings > Device health in the app. It lists the SMS permission, the Receive SMS toggle and battery settings that stop the app in the background.

Webhook or polling

Both methods return the same messages. Pick the one that fits your server.

WebhookPolling
LatencySeconds after the phone uploads the messageYour poll interval
Needs a public URLYes, HTTPS in productionNo
Retriestextbee retries failed deliveries, up to 10 attemptsNot needed. The next poll reads what you missed.
SetupA subscription and an endpoint that verifies the signatureA loop or a scheduled job
Pick it whenYou reply fast, for example to a code or a keywordYour server is behind a firewall, or you run a batch job

Many integrations use both. The webhook handles new messages, and a daily poll fills any gap.

Receive with a webhook

A webhook is a URL on your server. textbee sends an HTTP POST to it for each event you select.

  1. In the dashboard, open Webhooks and add a webhook.
  2. Enter your URL and a signing secret of at least 20 characters.
  3. Select the MESSAGE_RECEIVED event.
  4. Return a 2xx status quickly from your endpoint.

The full setup, the signature check and the retry rules are on the Webhooks page. Every event payload is on the Webhook events reference.

Receive by polling

The messages endpoint returns sent and received messages for every device on your account.

GET/api/v1/gateway/messages, open in the API reference

This request returns the 20 newest received messages:

Shell
curl "https://api.textbee.dev/api/v1/gateway/messages?direction=received&limit=20" \
  -H "x-api-key: YOUR_API_KEY"

To read only new messages on each run, ask for order=asc and follow the cursor. The next poll resumes from the last nextCursor you stored. This sample reads every received message since a start time:

poll.sh
BASE_URL="${TEXTBEE_BASE_URL:-https://api.textbee.dev/api/v1}"
QUERY="direction=received&order=asc&limit=50&from=2026-09-01T00:00:00Z"
CURSOR=""

while :; do
  PAGE=$(curl -sS "$BASE_URL/gateway/messages?$QUERY${CURSOR:+&cursor=$CURSOR}" -H "x-api-key: $TEXTBEE_API_KEY")
  echo "$PAGE" | jq -r '.data[] | "\(._id) \(.sender) \(.message)"'
  [ "$(echo "$PAGE" | jq -r '.meta.hasMore')" = "true" ] || break
  CURSOR=$(echo "$PAGE" | jq -r '.meta.nextCursor') # store this to resume the next poll
done

The cursor never skips a message and never returns one twice. Message history and polling has the full parameter table and more filters.

Response shape

Each received message in data looks like this excerpt:

JSON
{
  "data": [
    {
      "_id": "66f7c1e2a4d5e6f7a8b9c0d1",
      "message": "YES, please confirm my booking",
      "sender": "+12015550123",
      "receivedAt": "2026-09-28T09:14:03.000Z",
      "direction": "received",
      "status": "received",
      "device": {
        "_id": "664a9b8cd0e1f2a3b4c5d6e7",
        "enabled": true,
        "name": "Office phone",
        "brand": "google",
        "model": "Pixel 7",
        "buildId": "BP2A.250605.031"
      },
      "createdAt": "2026-09-28T09:14:05.112Z",
      "updatedAt": "2026-09-28T09:14:05.112Z"
    }
  ],
  "meta": {
    "limit": 20,
    "nextCursor": "eyJ0IjoiMjAyNi0wOS0yOFQwOToxNDowNS4xMTJaIn0",
    "hasMore": true
  }
}
FieldMeaning
senderNumber that sent the SMS, as the phone reports it
messageMessage text
receivedAtWhen the phone received the SMS
directionreceived for incoming messages
device._idDevice that received the message. Use it as deviceId.

Reply to a received message

To reply, send an SMS to the sender number with the send endpoint. Pass the device._id of the received message as deviceId so the reply goes out from the same phone.

Shell
curl -X POST https://api.textbee.dev/api/v1/gateway/send-sms \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipients": ["+12015550123"], "message": "Thanks, your booking is confirmed.", "deviceId": "664a9b8cd0e1f2a3b4c5d6e7"}'

On a phone with two SIMs, pass simSubscriptionId as well. See Choosing a SIM.

Notes

Messages received over the plan limit

A received message that arrives while your account is over its plan limit has overLimit: true. The field is absent on other messages. See Pricing for the limits of each plan.

Late uploads

A phone that was offline uploads its backlog when it reconnects. If the upload is more than 24 hours after receivedAt, textbee sets createdAt to receivedAt and stores the upload time in originalCreatedAt. The from and to filters work on createdAt, so the message lands in the time window when it was received.

If the upload is more than 48 hours after receivedAt, textbee stores the message but does not send a MESSAGE_RECEIVED webhook for it. A poll still returns it. This is one reason to run a periodic poll next to a webhook.

Filter messages on the phone

The app can forward only some messages, for example only messages from one short code. Filtered messages never leave the phone. See Filter incoming SMS on the phone.

Frequently asked questions

Why do I not see received messages?

Check four things. Receive SMS is on in the app. The SMS permission is granted. receiveSMSEnabled is true on the device. No SMS filter blocks the sender. The phone connection guide covers phones that stop in the background.

Can one account receive on several phones?

Yes. Each registered phone forwards its own messages. The messages endpoint returns all of them. Use deviceIds to narrow the list to one phone.

Does textbee receive MMS?

No. textbee receives SMS text messages only.

Do I need a webhook to receive messages?

No. The messages endpoint returns received messages without a webhook. A webhook only removes the delay of a poll.

Next steps