A two way sms api lets your product send a text with one request and then receive the contact's reply as data you can read, route and act on. With Ready, you send with POST /v1/messages, read replies with GET /v1/messages (or get them pushed by webhook), and STOP opt-outs are enforced for you before your code ever has to think about them.

Most SMS integrations start as one-way: order shipped, appointment tomorrow, code is 482913. Then someone on the product team asks for "reply C to confirm," or a customer texts back a question nobody is reading. At that point the job changes from sending messages to running conversations. You need threading, opt-out enforcement, a billing model that accounts for inbound traffic, and a decision about whether humans read replies in your UI or in a shared inbox. This post covers each piece using only the endpoints Ready actually exposes.

How a two way sms api differs from a broadcast API

A broadcast API covers one direction: you POST, the carrier delivers, and you get a delivery status. A two-way setup adds four responsibilities.

  1. Inbound capture. Replies arrive on the number you sent from. They have to be stored and tied to the right contact and sub-account.
  2. Opt-out enforcement. A reply of STOP is a legal instruction, not a message to read later. It has to block future sends right away, across every campaign.
  3. Routing. Some replies are machine-readable ("C", "YES") and some need a human. Your system decides which is which.
  4. Number stickiness. The contact should keep hearing from the same number. Otherwise the thread breaks on their phone and replies go to a number your app has stopped watching.

Ready handles the first two in the platform. The third is your product logic. The fourth is handled by how you choose from, which we cover below.

Sending a text with POST /v1/messages

Every request goes to https://api.tryready.com with your key in the X-API-Key header. Add an Idempotency-Key header with any UUID. If your worker retries after a timeout, the retry then can't send the text twice. This matters more in two-way flows, because a duplicate "Reply C to confirm" produces confused duplicate replies.

curl -X POST https://api.tryready.com/v1/messages \
  -H "X-API-Key: $READY_API_KEY" \
  -H "Idempotency-Key: <a new UUID for each text>" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+12125550199",
    "body": "Hi Dana, your cleaning is Thu at 2 PM. Reply C to confirm or R to reschedule.",
    "consent_attested": true
  }'

What each field does:

  • to is E.164. A bare 10-digit US number is treated as +1.
  • body is the text. It's billed per segment, so length matters (see the cost section).
  • consent_attested is your promise that this person agreed to receive texts. Ready records the attestation per send: who sent, to how many, when. That gives you an audit trail of sends. It is not a per-contact consent record. The opt-in time, wording and page URL for each contact belong in your own database, captured at signup.

Optional fields you'll want in a two-way product:

  • from: one of your numbers. Pin it per contact so the thread stays on one number.
  • from_strategy: local, even or random. local picks your number closest to the contact's area code. Local presence has a listed price of $0.0015 for each segment of a delivered text.
  • media_url: attach a picture and send it as MMS.

For the same text to many people, POST /v1/messages/bulk takes an array in to with the same body and consent_attested. The bulk messages docs cover limits. Our SMS API integration walk-through goes deeper on retries and error handling.

Two behaviors protect you on the send side. Opt-outs, numbers on your Ban list and known landlines are refused up front and not charged. An API or one-to-one text sent during the recipient's quiet hours is held and sent later if their window opens within about 6 hours; otherwise it is refused with 409 QUIET_HOURS_BLOCK and you send it again. Campaign and automation texts wait up to 24 hours. It's charged only when it actually sends.

Reading replies: polling vs webhooks

Polling GET /v1/messages

The simplest way to collect replies is to read the message list:

curl https://api.tryready.com/v1/messages \
  -H "X-API-Key: $READY_API_KEY"

Results are paged. To walk backwards through history, pass the ID of the oldest message you've seen as before_id:

curl "https://api.tryready.com/v1/messages?before_id=<oldest_id_you_have>" \
  -H "X-API-Key: $READY_API_KEY"

The messages API reference documents the record shape, including how to tell inbound from outbound. Use the documented fields rather than guessing.

Polling is fine for back-office jobs and nightly reconciliation. It's a poor fit for conversations. If a patient replies "R" and your poller runs every five minutes, they wait up to five minutes for "What time works instead?" On SMS, that's long enough for them to call the front desk.

Webhooks

For real-time replies, register a webhook so Ready pushes inbound messages to your endpoint as they arrive. Setup, signing and event types are in the Ready docs. Build from that reference, not from memory. A sensible pattern:

  • Acknowledge the webhook fast and process async. Queue the payload, return 2xx, and handle it in a worker.
  • Make your handler idempotent. Deliveries can repeat, so dedupe on the message ID.
  • Keep a periodic GET /v1/messages sweep as a safety net. It catches anything your endpoint missed during a deploy.

That combination of push for speed and a poll sweep for completeness is how most production two-way systems end up.

STOP handling: what Ready does and what you still own

When a contact replies STOP, UNSUBSCRIBE or a similar keyword, Ready honors it automatically. The opt-out propagates so that number can't be messaged again across campaigns. Any later POST /v1/messages to it is refused before it reaches a carrier, and you aren't charged for it.

That covers the carrier-level obligation. Three things still sit with you:

  • Mirror opt-outs into your own data. GET /v1/opt-outs lists the account's opted-out contacts; it leaves out a deleted contact's STOP, dialer do-not-call entries and STOPs on other sub-accounts of the same parent, so use GET /v1/opt-outs/:phone for the authoritative answer on one number. Then hide "send text" buttons and skip opted-out contacts in your own workflows. Users get confused when your UI lets them compose a message the API will refuse.
  • Don't build a flow that talks people out of opting out. A reply like "Are you sure? Reply KEEP to stay subscribed" is a compliance problem. Our post on handling SMS opt-outs covers the keyword rules and common mistakes.
  • Treat informal opt-outs seriously. "Please stop texting me" isn't a keyword, but it's a clear request. Route replies like that to a human or to a rule that suppresses the contact.

Compliance stays the sender's responsibility. Ready's STOP handling, quiet-hours holds and optional litigator/DNC scrubbing reduce risk. They don't make anyone lawsuit-proof.

What replies cost (worked example)

On Ready, a reply you receive is billed like a sent text: the per-segment rate plus the carrier fee. Teams moving from one-way to two-way often forget to budget for this.

Pricing on Standard is $0.0075/segment plus a $0.0045 carrier fee per segment, one flat rate with no tiers. That all-in figure excludes the compliance surcharge. Scrubbing and landline validation each add $0.003 per segment when switched on for an account, and both are off by default.

Example assumptions: a scheduling product sends 2,000 appointment reminders a month. Each reminder is 1 segment (under 160 plain characters, no emoji). We assume 35% of contacts reply once, and each reply is 1 segment.

LineSegmentsRate portion ($0.0075)Carrier portion
Outbound reminders2,000$15.002,000 × $0.0045
Inbound replies (35%)700$5.25700 × $0.0045
Total2,700$20.252,700 × $0.0045

Replies add 35% to the segment count in this example. Conversational products with back-and-forth threads can easily double it.

Inbound segments follow the same math as outbound, and you don't control what customers type. "C" is 1 segment. A 150-character reply with one emoji is 3 segments, because an emoji drops the limit to 70 characters (67 per part in multipart). Ready swaps curly quotes, long dashes and ellipses for plain ones on your outbound text, so those never inflate your count. You can't edit a customer's emoji, though. Budget a little headroom for inbound. Our breakdown of carrier fee pricing explains why that fee is itemized separately on the bill.

On price per segment, a raw CPaaS such as Twilio (~$0.0083/segment) costs less, and so does GoHighLevel's LC Phone (~$0.0083/msg, about $0.0128 all-in). If your team wants to build registration flows, an inbox, opt-out sync and quiet-hours logic itself, that lower rate is a real saving. What Ready includes is that surrounding stack. Self-serve 10DLC registration is handled in-app, and most approvals take 2 to 7 days. STOP enforcement, quiet-hours holds, an Inbox and sub-accounts come with it.

Registration and sub-accounts for platforms

Two-way traffic runs on registered 10DLC routes like everything else. Unregistered traffic gets carrier-filtered, and a filtered reminder never gets a reply. Registration costs $25 at submit, which is the first month. After that it's $25/mo for registration and $2/month per number. You can check status with GET /v1/registration.

Before registering, an account can send up to 500 texts a month at its normal per-text rate. The first 20 texts are free one-to-one sends, so use them for testing before you register, because they end when a registration is paid for. To send, you add a card and get a number, which is $2 a month with the first month charged when you get it.

If you're a platform texting on behalf of your own customers, create a sub-account per customer with POST /v1/sub-accounts. Each one gets its own numbers and its own registration. Replies then land in the right customer's data, and one customer's bad list can't damage another's sender reputation. Pick the campaign use case that matches what's actually sent. A reminder flow registered as marketing tends to get throttled.

When a shared inbox beats building your own

Building a two-way UI looks small until you list what it needs: threaded view per contact, unread state, assignment, search, mobile notifications, attachment rendering and opt-out badges. That's a quarter of product work for a feature that may not be your product's core.

Use Ready's Inbox (or GHL sync) when:

  • Replies are read by your customers' staff (front desks, sales reps, coordinators) rather than parsed by code.
  • You need two-way working this month, not next quarter.
  • Your customers already live in GoHighLevel. Ready's native integration syncs inbound and outbound messages both ways, mapped per GHL location, so threads stay intact. Our post on native vs webhook GHL sync explains what a webhook-only wiring loses.

Build your own on the API when:

  • Replies are structured and machine-handled (confirm/reschedule codes, surveys, status lookups).
  • The conversation has to sit inside your product's own records, such as a patient chart, a job ticket or a deal page.
  • You already have a messaging UI and only need the transport.

Plenty of teams do both. Machine-readable replies are handled by code via webhook, and everything else falls through to the shared inbox for a human. Ready's inbox also shows blue line (iMessage) replies, reactions and typing indicators in the same thread as SMS if you add a blue line later. Blue line replies are priced differently, at 6 cents a segment.

The practical takeaway

A working two-way setup has five parts:

  • Idempotent sends.
  • A webhook for real-time replies plus a polling sweep as backup.
  • Opt-outs mirrored into your own data.
  • Reply volume included in your budget.
  • A deliberate decision about whether humans read replies in your UI or in a shared inbox.

Ready provides the transport, registration, STOP enforcement and inbox. You provide the routing logic and the consent records.

If you want to try it, the quickstart gets a first send working, and you can create an account here. Use the free one-to-one texts to test your reply handling before you register.

FAQ

What is a two-way SMS API? A two-way SMS API lets software both send text messages and receive the replies as data. You send with one request and get replies by polling or by webhook. That makes it possible to build confirmations, support threads and conversational flows instead of one-way notifications.

Do I get charged for incoming SMS replies? On Ready, yes: a reply you receive is billed like a sent text, at $0.0075/segment plus the $0.0045 carrier fee. Long replies or replies containing emoji can be several segments, so budget some headroom for inbound volume.

How do I receive SMS replies in real time? Register a webhook so inbound messages are pushed to your endpoint as they arrive. Setup details are in the Ready docs at docs.tryready.com. Keep a periodic GET /v1/messages sweep as a backup for anything missed during downtime.

What happens when someone texts STOP to my number? Ready honors the opt-out automatically and blocks that number from future sends across all campaigns. Later send attempts to it are refused and not charged. You should still sync opt-outs with GET /v1/opt-outs so your own UI reflects them. That list holds only this account's opted-out contacts, so check GET /v1/opt-outs/:phone before another tool texts a number.

Should I build my own SMS inbox or use a shared one? Use a shared inbox if humans read most replies and you need two-way working quickly. Build your own on the API if replies are structured codes your system handles, or if conversations must live inside your product's records. Many teams combine both: code handles predictable replies and the rest goes to a shared inbox.