Challenges API
Envia codigos de verificacion por email, SMS o WhatsApp a cualquier dispositivo del mundo con maxima entrega, cero spam y los mejores precios del mercado.
Seguro
HMAC server-to-server, tokens temporales y comparacion en tiempo constante.
RESTful
Endpoints claros y predecibles con JSON, idempotency y rate limits integrados.
Rapido
Flujo separado backend/frontend que minimiza latencia y evita replay/tampering.
Arquitectura del flujo
- 1Tu backend crea el challenge (POST /v1/challenges/send) con autenticacion segura.
- 2El usuario recibe un OTP (email/sms).
- 3El frontend hace exchange del OTP para obtener un verificationToken temporal.
- 4El frontend verifica el challenge con Bearer token.
- 5El backend valida el assertionToken recibido y crea sesion en tu sistema.
Authentication
Dos metodos de autenticacion soportados segun el contexto de la llamada.
HMAC (Server-to-Server)
Para llamadas desde tu backend. Requiere firmar cada request con tu API secret.
Que es HMAC y por que lo usamos?
HMAC (Hash-based Message Authentication Code) es un mecanismo que permite verificar que un request fue enviado por alguien que conoce el API Secret, sin necesidad de enviarlo en texto plano. Tu backend firma el contenido del request usando el secret como clave y envia esa firma en un header. El servidor de Trumi recalcula la firma con el mismo secret y si ambas coinciden, el request es autentico.
Headers requeridos
X-API-Key: <api_key>
X-Signature: sha256=<hex_hmac>
X-Timestamp: <unix_epoch_seconds>
Content-Type: application/jsonhttpString to Sign
{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_SHA256_HEX}textNode.js Example
import crypto from 'crypto';
function buildHmacHeaders(
method: string,
path: string,
bodyText: string,
apiKey: string,
apiSecret: string
) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto
.createHash('sha256')
.update(bodyText)
.digest('hex');
const stringToSign = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
const signature = crypto
.createHmac('sha256', apiSecret)
.update(stringToSign)
.digest('hex');
return {
'X-API-Key': apiKey,
'X-Timestamp': timestamp,
'X-Signature': `sha256=${signature}`,
'Content-Type': 'application/json'
};
}typescriptValidaciones de seguridad
- Ventana de timestamp: +/- 5 minutos.
- Comparacion de firma en tiempo constante.
- Integridad de body via hash SHA-256.
- Secretos cifrados en KMS + envelope encryption.
Bearer Token (Frontend)
Token temporal usado en POST /v1/challenges/verify desde el navegador.
Authorization: Bearer <verificationToken>
Content-Type: application/jsonhttpEndpoints
Referencia completa de los endpoints de la API de Challenges.
/v1/challenges/sendSend Challenge
Crea y envia un challenge OTP al usuario. Soporta idempotency via header Idempotency-Key.
{
"channel": "email",
"destination": "user@example.com",
"purpose": "login",
"verificationType": "code",
"state": "opaque-client-state",
"preferredLanguages": ["es", "en"]
}json/v1/challenges/exchangeExchange Code
Intercambia challengeId + code por un verificationToken temporal de corta vida. One-time: el codigo se marca como usado.
{
"challengeId": "chg_...",
"code": "123456"
}json/v1/challenges/verifyVerify Challenge
Verifica el challenge usando el verificationToken en Authorization: Bearer.
{
"code": "123456"
}jsonPara verificationType=code (default), code es requerido.
Para variantes temporales/magic-link, el backend puede permitir validacion solo con token.
/v1/challenges/validate-assertionValidate Assertion
Valida en tu backend un assertionToken emitido por /verify. Uso recomendado: operaciones sensibles (fraude, account changes, pagos).
{
"assertionToken": "<payload.signature>"
}jsonManagement Endpoints
/v1/challengesListado paginado (JWT admin)/v1/challenges/{id}501 Not Implemented/v1/challenges/{id}/cancel501 Not ImplementedFlows
Flujo recomendado de integracion: Backend + Frontend + Assertion.
POST /v1/challenges/send
Tu backend crea el challenge con HMAC + Idempotency-Key
Recibe OTP
El usuario recibe el codigo por email, SMS o WhatsApp
POST /v1/challenges/exchange
Frontend envia challengeId + code para obtener verificationToken
POST /v1/challenges/verify
Frontend verifica con Bearer token y recibe assertionToken
Envia assertionToken
Frontend envia el assertionToken a tu backend
POST /v1/challenges/validate-assertion
Backend valida el assertionToken y crea sesion en tu sistema
Security
Mecanismos de idempotencia, proteccion anti-replay y checklist de seguridad.
Idempotency
Proteccion contra envios duplicados usando el header Idempotency-Key.
Idempotency-Key: <uuid-v4-or-unique-client-key>http| Escenario | Comportamiento |
|---|---|
| Mismo key + mismo payload | Respuesta cacheada + X-Idempotency-Cached: true |
| Mismo key + payload distinto | 409 Idempotency-Key reused with different payload |
| Request en progreso | 202 Processing |
| Sin Idempotency-Key | Request normal (sin proteccion idempotente) |
TTL por defecto: 3600s (IDEMP_TTL_SEC)
Checklist de Seguridad
Rate Limits
Rate limiting multicapa para proteger el servicio contra abuso.
Capas de limitacion
Respuesta tipica
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"limit": 6,
"current": 7,
"resetTime": 1760400300
}jsonBuenas practicas
- Aplicar exponential backoff.
- Respetar Retry-After cuando exista.
- Evitar retries agresivos en send.
Errors
Catalogo completo de errores por endpoint.
| Status | Error Code | Endpoints |
|---|---|---|
| 400 | VALIDATION_ERROR | send, exchange, verify |
| 401 | UNAUTHORIZED | send, verify |
| 401 | INVALID_CODE | exchange, verify |
| 401 | INVALID_TOKEN | validate-assertion |
| 403 | CODE_ALREADY_USED | exchange |
| 403 | CHALLENGE_FAILED | exchange, verify |
| 403 | CORS_ORIGIN_NOT_ALLOWED | exchange |
| 403 | CHALLENGE_NOT_VERIFIED | validate-assertion |
| 404 | MISSING_CHALLENGE | exchange, verify |
| 404 | CHALLENGE_NOT_FOUND | validate-assertion |
| 410 | CHALLENGE_EXPIRED | exchange, verify |
| 429 | RATE_LIMIT_EXCEEDED | send, exchange |
| 500 | INTERNAL_ERROR | send |
Quickstart
Ejemplo end-to-end rapido con cURL para probar el flujo completo.
Send (HMAC + Idempotency)
curl -X POST "https://api.example.com/v1/challenges/send" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: sha256=$SIG" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"channel":"email","destination":"user@example.com","purpose":"login"}'bashExchange (Frontend)
curl -X POST "https://api.example.com/v1/challenges/exchange" \
-H "Content-Type: application/json" \
-H "Origin: https://verify.example.com" \
-d '{"challengeId":"chg_...","code":"123456"}'bashVerify (Bearer Token)
curl -X POST "https://api.example.com/v1/challenges/verify" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $VERIFICATION_TOKEN" \
-d '{"code":"123456"}'bashValidate Assertion (Backend)
curl -X POST "https://api.example.com/v1/challenges/validate-assertion" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: sha256=$SIG" \
-d '{"assertionToken":"'$ASSERTION_TOKEN'"}'bash