For developers and QA
Receive SMS on a real US number, straight into your code.
A real US mobile line, not VoIP, that hands every incoming text to your code: a signed webhook as soon as the text is stored, and a REST API to list and poll. People on your team still get the live inbox, email and Telegram. One line, $24.99 a month, cancel anytime.
Why not a number from a telecom platform?
Programmable numbers from telecom platforms are usually VoIP by classification. Twilio's own post on filtering VoIP before sending codes shows its Lookup flagging a Twilio number as nonFixedVoip (Twilio). If the service you're signing up to, or testing against, filters VoIP, its code never reaches a platform number. A Cleat line is a mobile line. That's the whole reason to use one. More on line types.
Quick start
- Get a line. Sign up, verify your identity and subscribe.
- Create an API key in Settings. It starts with
clt_and is shown once. - Add a webhook endpoint in Settings, if you want pushes: a public HTTPS URL. Its signing secret starts with
whsec_and is shown once. - List your lines, then poll for texts or wait for webhooks.
curl https://cleat.so/api/v1/lines \
-H "Authorization: Bearer $CLEAT_API_KEY"{
"data": [
{
"id": "8f14e45f-ceea-4b6b-9d3c-2a1f0e7c5b10",
"phone": "13055550100",
"label": "Staging sign-ups",
"status": "active",
"createdAt": "2026-09-11T10:00:00.000Z"
}
]
}curl "https://cleat.so/api/v1/lines/$LINE_ID/messages?after=2026-09-11T10:00:00Z&limit=50" \
-H "Authorization: Bearer $CLEAT_API_KEY"{
"data": [
{
"id": "c9a7e0d2-5b1f-4e8a-9f3c-6d2b1a0e4f77",
"line": { "id": "8f14e45f-ceea-4b6b-9d3c-2a1f0e7c5b10", "phone": "13055550100", "label": "Staging sign-ups" },
"from": "55512",
"body": "Your verification code is 704 118",
"code": "704118",
"receivedAt": "2026-09-11T10:02:41.000Z"
}
]
}Webhooks
When a text arrives, Cleat sends a POST to each of your endpoints with this JSON body, a cleat-signature header and the user agent Cleat-Webhooks/1.0:
{
"type": "message.received",
"data": {
"id": "c9a7e0d2-5b1f-4e8a-9f3c-6d2b1a0e4f77",
"line": { "id": "8f14e45f-ceea-4b6b-9d3c-2a1f0e7c5b10", "phone": "13055550100", "label": "Staging sign-ups" },
"from": "55512",
"body": "Your verification code is 704 118",
"code": "704118",
"receivedAt": "2026-09-11T10:02:41.000Z"
}
}The signature header looks like t=1789120961,v1=5d41…. v1 is the hex HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the endpoint's signing secret. Check it against the raw bytes before you parse them, and reject old timestamps so a captured delivery can't be replayed.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.CLEAT_WEBHOOK_SECRET; // whsec_…
// cleat-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">
function verified(header, body) {
const parts = Object.fromEntries(String(header).split(",").map((part) => part.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
app.post("/cleat-webhook", express.raw({ type: "application/json" }), (req, res) => {
const body = req.body.toString("utf8");
if (!verified(req.get("cleat-signature"), body)) return res.sendStatus(401);
const { type, data } = JSON.parse(body);
if (type === "message.received") {
// data.code is Cleat's best guess at a one-time code; data.body is the full text.
console.log(data.line.phone, data.from, data.code ?? data.body);
}
res.sendStatus(200);
});
app.listen(3000);Delivery details
- Endpoints must be public
https://URLs. Cleat doesn't follow redirects. - Answer with any 2xx status within 10 seconds.
- Each text is posted once. If your endpoint was down, catch up with the API using
?after=and thereceivedAtof the last text you handled. A text'sidnever changes, so use it to skip duplicates. - Up to five endpoints per workspace. Settings shows each endpoint's last status and how many deliveries in a row have failed.
codeis Cleat's best guess at a one-time code, ornull. The fullbodyis always there.- A code read out over an automated call arrives the same way: the call is transcribed and delivered as a
message.received, with the transcript inbody, the number that called infrom, andcodefilled in. Nothing in the payload marks a delivery as having been a call rather than a text.
API reference
| Endpoint | What it returns |
|---|---|
GET /api/v1/lines | The lines in the API key's workspace, newest first: id, phone, label, status (active, grace or released) and createdAt. |
GET /api/v1/lines/{lineId}/messages | Texts on one line. after (ISO 8601) returns texts received after that moment, oldest first, for polling. before pages back, newest first. limit is 1 to 200, 50 by default. |
Authorization: Bearer clt_…. Errors: 400 for a malformed timestamp, 401 for a missing or revoked key, 402 while a line is on hold, 404 for a line outside the key's workspace, 429 over the rate limit.Limits and permissions
120 requests a minute per API key; past that the API answers 429. Only the workspace owner can create API keys and webhook endpoints, and a revoked key stops working immediately.
Use cases that fit
- Your company's own accounts, such as cloud consoles, registrars and payment processors, with codes posted to a channel your on-call engineer watches.
- Staging and QA of your own sign-up and login flows, against a number that behaves like a customer's phone.
- Personal projects that need a US mobile number on file.
Use cases that don't
- Sending SMS. Cleat Line is receive-only.
- Load testing or pools of numbers. Each line is a separate subscription with a verified owner. Cleat isn't built for number pools.
- Accounts that aren't yours or your company's.
- Anything that needs live voice. A line can't dial out, and there is no ringing to answer, no audio to stream and no recording to fetch — only the transcript of a call after it has ended.
How it compares
| Telecom platform number | MailSlurp phone numbers | Cleat Line | |
|---|---|---|---|
| Number type | Often VoIP by classification; Twilio's Lookup flags a Twilio number as nonFixedVoip | “dedicated, non-VoIP mobile numbers” (MailSlurp) | Real US mobile line, not VoIP |
| Built for | Sending and receiving at scale | QA and development testing | Your own accounts, QA and small teams |
| Owner verification | Business KYC and 10DLC registration | Not stated | Government ID and selfie, or ID-only reviewed by a person |
| Getting texts into code | Webhooks and API | API and webhooks | Signed webhooks and REST API, plus Telegram, email and the inbox |
| Price | Per number plus per message | Not shown on the page | $24.99/month, incoming texts included |
Sharing codes with people who don't write code? See shared 2FA for teams. Why Google Voice and other internet numbers miss codes: Google Voice and verification codes. How we handle identity: trust and safety. Full billing rules: pricing.
Questions
Is there a free tier or sandbox?
No. One line is $24.99 a month, cancel anytime, and nobody gets a line before verifying their identity.
Can I send SMS through the API?
No. Cleat Line receives; it never sends.
What if a service phones the code instead of texting it?
The call is transcribed and reaches your webhook and the API as an ordinary message, with the code extracted and the caller's number in from. There's no field that tells you it was a call.
Are there rate limits?
Yes: 120 requests a minute per API key. Webhook deliveries don't count against it.
How fast do webhooks fire?
Cleat posts to your endpoints as soon as it has stored the text, alongside the live inbox, email and Telegram. We haven't published a measured latency, so we don't quote one.
Can I get more than one number?
You can add lines to a workspace, and each one is its own $24.99 subscription. Cleat isn't built for pools of numbers.
Do you keep my messages?
Texts are kept until you delete the workspace or your account. The Privacy Policy has the details.
Sources
Facts about other companies come from their own pages, checked in September 2026.
- Twilio, filtering VoIP numbers before sending codes https://www.twilio.com/en-us/blog/filter-voip-before-otp-verification
- MailSlurp, phone numbers https://www.mailslurp.com/product/phone-number-sms-service/