Receiving SMS into your app with a webhook from your own Android number
Published and updated
Replies, keywords, inbound orders and verification codes from third parties all arrive as texts to a phone. Your application needs them as data, within seconds, without a person reading the phone. With textbee the sender is an Android phone holding your SIM, so the message comes from your own number, recipients can reply, and the Pro plan, up to 5,000 messages for $9.99 a month with no per message fee.
How it works
- Put the SIM that receives the messages in the textbee device and enable receiving on it, so every inbound text is stored on your account.
- Register a webhook URL in the dashboard for MESSAGE_RECEIVED with a signing secret of at least twenty characters.
- In the handler read the raw body, verify the HMAC-SHA256 in the X-Signature header, deduplicate on idempotencyKey, respond 200 fast, and process the message in a job.
- Add a scheduled poll of /gateway/messages with a cursor as the safety net, so anything missed while your endpoint was down is picked up.
Send it
The API key comes from the dashboard and the phone registered on your account does the sending. Recipients are E.164 numbers; the example is fictional.
import { createHmac, timingSafeEqual } from 'node:crypto'
import { createServer } from 'node:http'
const SECRET = process.env.TEXTBEE_WEBHOOK_SECRET
createServer((request, response) => {
const chunks = []
request.on('data', (chunk) => chunks.push(chunk))
request.on('end', () => {
const rawBody = Buffer.concat(chunks)
const expected = createHmac('sha256', SECRET).update(rawBody).digest('hex')
const signature = request.headers['x-signature'] ?? ''
if (signature.length !== expected.length || !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
response.writeHead(401).end()
return
}
const event = JSON.parse(rawBody.toString())
if (event.webhookEvent === 'MESSAGE_RECEIVED') console.log(`${event.sender}: ${event.message}`)
response.writeHead(200).end()
})
}).listen(process.env.PORT ?? 3000)Consent and compliance
Inbound messages are data people sent you, so store only what you need, for as long as you need it, and honour a STOP inside any inbound text as an opt-out from your outbound messages. If the number is shared with a person, keep their private conversations out of your application.
Honest limits
Webhooks are delivered when the phone has uploaded the message, so a phone that is offline delivers its backlog later; the poll with a cursor catches that. Retries happen when your endpoint fails, and a subscription that keeps failing is paused, so return 200 before doing slow work.
One phone sends roughly 10 to 15 messages a minute, so a large blast takes time to drain. Carriers can filter bulk patterns on consumer SIMs. Marketing messages still need consent from the recipient under the local rules.
Frequently asked questions
Do I need a public URL to receive webhooks?
Yes, reachable over HTTPS. Private and loopback addresses are rejected. In development use a tunnel or a staging deployment, or poll /gateway/messages instead.
How do I verify the webhook came from textbee?
Compute HMAC-SHA256 over the raw request body with the secret you set on the subscription and compare it in constant time with the X-Signature header. The sample above does exactly that.
Can I receive on more than one number?
Yes. Each device with a SIM receives on its number, and every received message on the account carries the device it arrived on. One webhook subscription covers all of them.
What about MMS or pictures?
textbee receives SMS. A picture message is not delivered to your application.