Para desarrollo y QA
Recibe SMS en un número real de Estados Unidos, directo en tu código.
Una línea de celular real de Estados Unidos, no VoIP, que le entrega cada SMS entrante a tu código. Un webhook firmado sale en cuanto se guarda el mensaje. Una API REST te deja listarlos y consultarlos. Tu equipo sigue viendo la bandeja en vivo, el correo y Telegram. Una línea, USD 24.99 al mes, cancela cuando quieras.
¿Por qué no un número de una plataforma de telecom?
Los números programables de las plataformas de telecom suelen clasificarse como VoIP. El propio artículo de Twilio sobre filtrar VoIP antes de mandar códigos muestra su Lookup marcando un número de Twilio como nonFixedVoip (Twilio). Si un servicio filtra VoIP, su código nunca llega a un número de plataforma. Una línea Cleat es una línea móvil. Esa es toda la razón para usarla. Más sobre tipos de línea.
Primeros pasos
- Consigue una línea. Regístrate y suscríbete. Listar tus líneas funciona desde el primer momento; leer los mensajes de una línea responde
403hasta que el dueño de la cuenta verifica su identidad. - Crea una API key en Ajustes. Empieza con
clt_y se muestra una sola vez. Si prefieres generar un cliente, el contrato completo está descrito en OpenAPI. - Agrega un endpoint de webhook en Ajustes, si quieres recibir pushes: una URL HTTPS pública. Su secreto de firma empieza con
whsec_y se muestra una sola vez. - Lista tus líneas y luego consulta los SMS o espera los 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": "32665",
"body": "704118 is your Facebook confirmation code",
"code": "704118",
"receivedAt": "2026-09-11T10:02:41.000Z",
"service": { "id": "facebook", "name": "Facebook", "color": "#0866FF" },
"contact": null,
"label": "Facebook"
}
]
}Webhooks
Cuando llega un SMS, Cleat manda un POST a cada uno de tus endpoints con este cuerpo JSON, un header cleat-signature y el 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": "32665",
"body": "704118 is your Facebook confirmation code",
"code": "704118",
"receivedAt": "2026-09-11T10:02:41.000Z",
"service": { "id": "facebook", "name": "Facebook", "color": "#0866FF" },
"contact": null,
"label": "Facebook"
}
}El header de firma se ve así: t=1789120961,v1=5d41…. v1 es el HMAC-SHA256 en hexadecimal del timestamp, un punto y el cuerpo crudo, con la clave del secreto de firma del endpoint. Verifícalo contra los bytes crudos antes de parsearlos. Rechaza los timestamps viejos para bloquear reenvíos.
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);Detalles de la entrega
- Los endpoints deben ser URLs
https://públicas. Cleat no sigue redirecciones. - Responde con cualquier estado 2xx en menos de 10 segundos.
- La entrega es al menos una vez. Si una falla o se pasa de tiempo, se reintenta: cinco intentos en total, el primero de inmediato y luego a los 30 segundos, 2 minutos, 10 minutos y una hora, enviando los mismos bytes con una firma nueva. Haz que tu handler sea idempotente sobre
data.id; elidde un SMS nunca cambia. Ajustes guarda cada intento y puede reenviar cualquier entrega. - Si fallan los cinco intentos, ponte al día con la API usando
?after=y elreceivedAtdel último SMS que procesaste. - Mientras la línea está en pausa o su dueño no ha verificado su identidad no se manda nada. Esos SMS se guardan y se pueden leer en cuanto se resuelve el motivo, pero después no se envía ningún webhook por ellos: léelos con
?after=. - Hasta cinco endpoints por espacio de trabajo. Ajustes muestra el último estado de cada endpoint y cuántas entregas seguidas han fallado.
typeesmessage.received, otestpara la entrega de prueba que puedes lanzar desde Ajustes: el mismodata, con sus propios ids. Responde 200 a un tipo que no conozcas; puede que se agreguen más.- Si en lugar de tu URL pegas un webhook entrante de Slack o un enlace de workflow de Microsoft Teams, ese endpoint recibe el formato de mensaje de esa app y sin
cleat-signature: con una app de chat no hay secreto compartido. codees la mejor suposición de Cleat sobre el código de un solo uso, onull. Elbodycompleto siempre está.labeldice de quién es el SMS en palabras: un contacto que guardó tu equipo o, si no, el servicio que Cleat reconoció por lo que dice el SMS, concontactyservicedetrás. Los tres sonnullcuando no hay certeza. Se agregaron después y no quitan nada:from,bodyycodesiguen igual.- Un código dictado en una llamada automática llega igual. La llamada se transcribe y se entrega como un
message.received, con la transcripción enbody, el número que llamó enfromycodelleno. Nada en el payload indica que la entrega fue una llamada y no un SMS.
Referencia de la API
| Endpoint | Qué devuelve |
|---|---|
GET /api/v1/lines | Las líneas del espacio de trabajo de la API key, de la más nueva a la más vieja: id, phone, label, status (active, grace o released) y createdAt. |
GET /api/v1/lines/{lineId}/messages | Los SMS de una línea. after (ISO 8601) devuelve los SMS recibidos después de ese momento, del más antiguo al más reciente, para consultar en bucle. before pagina hacia atrás, del más nuevo al más viejo. limit va de 1 a 200, y son 50 por defecto. |
Authorization: Bearer clt_…. Un limit fuera de 1–200 se ajusta al rango, no se rechaza. Errores: 400 si el timestamp está mal formado, 401 si falta la clave, está revocada o venció (code key_expired), 402 mientras la línea está en pausa, 403 hasta que el dueño del espacio de trabajo verifica su identidad (code verify_first), 404 si la línea está fuera del espacio de trabajo de la clave *o* fuera de su alcance: a propósito es la misma respuesta, 429 al pasar el límite, 503 si no se puede comprobar el alcance de la clave.Límites y permisos
120 peticiones por minuto por API key; al pasarte, la API responde 429, sin cabecera Retry-After, así que el backoff es tuyo. Solo el dueño del espacio de trabajo puede crear API keys y endpoints de webhook, y una clave revocada deja de funcionar de inmediato. También puedes limitar una clave al crearla: a líneas concretas y a una fecha en la que deja de servir, que es lo que hace seguro dársela a un agente, a un job de CI o a alguien externo. Una línea fuera del alcance responde 404, y una clave vencida responde 401 con code key_expired.
Casos que encajan
- Las cuentas propias de tu empresa, como consolas de nube, registradores de dominios y procesadores de pagos, con los códigos publicados en un canal que vigila la persona de guardia.
- Staging y QA de tus propios flujos de registro e inicio de sesión, contra un número que se comporta como el teléfono de un cliente.
- Proyectos personales que necesitan un número de celular de Estados Unidos registrado.
Casos que no
- Enviar SMS. Cleat Line solo recibe: no puede enviar mensajes, hacer llamadas ni marcar al 911.
- Pruebas de carga o grupos de números. Cada línea es una suscripción aparte con un dueño verificado. Cleat no está hecho para manejar muchos números.
- Cuentas que no son tuyas ni de tu empresa.
- Cualquier cosa que necesite voz en vivo. La API no puede llamar. No hay timbre que contestar, ni audio que transmitir, ni grabación que descargar. Solo queda la transcripción de una llamada después de que terminó. Las llamadas de Cleat Full se contestan y se hacen en la app web, no por la API.
Cómo se compara
| Número de plataforma de telecom | Números de MailSlurp | Cleat Line | |
|---|---|---|---|
| Tipo de número | Muchas veces VoIP por clasificación; el Lookup de Twilio marca un número de Twilio como nonFixedVoip | “dedicated, non-VoIP mobile numbers” (MailSlurp) | Línea de celular real de Estados Unidos, no VoIP |
| Hecho para | Enviar y recibir a gran escala | QA y pruebas de desarrollo | Tus propias cuentas, QA y equipos pequeños |
| Verificación del dueño | KYC de empresa y registro 10DLC | No lo indica | Identificación oficial y selfie, o solo identificación revisada por una persona |
| Llevar los SMS al código | Webhooks y API | API y webhooks | Webhooks firmados y API REST, además de Telegram, correo y la bandeja |
| Precio | Por número más por mensaje | No aparece en la página | USD 24.99 al mes, SMS entrantes incluidos |
¿Compartes códigos con gente que no programa? Mira 2FA compartido para equipos. Por qué a Google Voice y a otros números de internet no les llegan los códigos: Google Voice y los códigos de verificación. Cómo manejamos la identidad: confianza y seguridad. Las reglas completas de cobro: precio.
Preguntas
¿Hay plan gratis o sandbox?
No. Una línea cuesta USD 24.99 al mes, cancelas cuando quieras, y sus mensajes quedan bloqueados hasta que su dueño verifica su identidad.
¿Puedo enviar SMS por la API?
No. Cleat Line recibe; nunca envía.
¿Y si un servicio dicta el código en una llamada en vez de mandarlo por SMS?
La llamada se transcribe y llega a tu webhook y a la API como un mensaje normal, con el código extraído y el número que llamó en from. Ningún campo indica que fue una llamada.
¿Hay límites de peticiones?
Sí: 120 peticiones por minuto por API key. Las entregas de webhook no cuentan para ese límite.
¿Qué tan rápido salen los webhooks?
Cleat manda el POST a tus endpoints en cuanto guardó el SMS, junto con la bandeja en vivo, el correo y Telegram. No hemos publicado latencia medida, así que no damos un número.
¿Puedo tener más de un número?
Puedes agregar líneas a un espacio de trabajo, y cada una es su propia suscripción de USD 24.99. Cleat no está hecho para manejar muchos números.
¿Guardan mis mensajes?
Los SMS se guardan hasta que borras el espacio de trabajo o tu cuenta. La Política de privacidad, en inglés tiene los detalles.
Fuentes
Los datos sobre otras empresas vienen de sus propias páginas, revisadas en septiembre de 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/