Gestión de errores
Todas las respuestas de error siguen una única estructura JSON. Usa los códigos de estado HTTP para enrutar tu lógica de gestión de errores, y el campo error para el contexto legible por humanos.
// Every non-2xx response returns:
{
"error": "human-readable description of what went wrong"
}Códigos de error
La tabla a continuación cubre todos los códigos de estado devueltos por Connect Hub. La columna Reintentar indica si el reintento automático es seguro.
| Código | Nombre | Reintentar | Descripción |
|---|---|---|---|
400 | Bad Request | NO | Campos faltantes o inválidos (p. ej. teléfono no en formato E.164, parámetro de cuerpo obligatorio faltante). Corrige tu payload. |
401 | Unauthorized | NO | Encabezado x-api-key faltante o inválido. Verifica tu clave de API: no reintentas automáticamente. |
402 | Payment Required | NO | Saldo de cuenta insuficiente. Recarga tu cuenta de Novauth antes de reintentar. |
403 | Forbidden | NO | La clave de API no tiene permiso para realizar esta acción. |
404 | Not Found | NO | El recurso no existe (p. ej. UUID desconocido en /call/flash/{uuid}/cdr). No reintentas. |
409 | Conflict | NO | Se detectó una operación duplicada o conflictiva (p. ej. reportar webhook dos veces para el mismo UUID). |
422 | Unprocessable Entity | NO | Específico de Telegram: solicitud semánticamente inválida (p. ej. request_id expirado en check-verification-status). |
429 | Too Many Requests | YES | Límite de velocidad excedido. Respeta el encabezado Retry-After e implementa retroceso exponencial. |
480 | Temporarily Unavailable | YES | El destinatario no está disponible temporalmente (derivado de SIP). Es seguro reintentar tras una breve espera. |
482 | Loop Detected | NO | Se detectó un bucle de enrutamiento de solicitud. No reintentas: investiga tu integración. |
486 | Busy Here | YES | El destinatario está ocupado. Reintenta tras una espera o cambia a otro canal. |
488 | Not Acceptable Here | NO | Parámetros de solicitud no aceptables (p. ej. codec o formato no admitido). Corrige la solicitud. |
500 | Internal Server Error | YES | Fallo inesperado del lado del servidor. Reintenta con retroceso exponencial (máx. 3 intentos). |
502 | Bad Gateway | YES | El servicio upstream (operador, gateway) devolvió una respuesta inválida. Reintenta tras una breve espera. |
503 | Service Unavailable | YES | Servicio temporalmente sobrecargado o en mantenimiento. Respeta el encabezado Retry-After si está presente. |
603 | Decline | NO | El destinatario rechazó explícitamente la llamada (derivado de SIP). No reintentas: el usuario rechazó activamente la llamada. |
Lógica de reintento
Reintenta solo en los códigos 429, 480, 486, 500, 502, 503. Usa retroceso exponencial comenzando en 500 ms (duplica cada intento). Nunca reintentas automáticamente errores 4xx del cliente: corrige primero la causa raíz.
async function callWithRetry(fn, { maxAttempts = 3, baseDelay = 500 } = {}) {
// Safe codes to retry: 429, 500, 502, 503
const RETRYABLE = new Set([429, 500, 502, 503]);
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fn();
if (res.ok) return res;
const { status } = res;
// Non-retryable — surface error immediately
if (!RETRYABLE.has(status)) {
const body = await res.json().catch(() => ({}));
throw Object.assign(new Error(body.error ?? 'API error'), { status, body });
}
if (attempt === maxAttempts) {
throw Object.assign(new Error('Max retries reached'), { status });
}
// Exponential backoff: 500ms, 1000ms, 2000ms …
const delay = baseDelay * 2 ** (attempt - 1);
await new Promise(r => setTimeout(r, delay));
}
}
// Usage
const res = await callWithRetry(() =>
fetch('https://api.novauth.com/api/v1/connect-hub/call/flash', {
method: 'POST',
headers: {
'x-api-key': process.env.BETATEL_API_KEY,
'x-account-id': process.env.BETATEL_ACCOUNT_ID,
'Content-Type': 'application/json'
},
body: JSON.stringify({ callee: '+14155552671' })
})
);Las operaciones idempotentes (comprobaciones de estado GET) pueden reintentarse libremente. Para operaciones POST que crean recursos, comprueba siempre si el recurso ya fue creado antes de reintentar: usa el UUID devuelto para consultar el estado primero.
Cascada de respaldo
Verify Call funciona en la mayoría de las regiones, pero no es universal. Implementa una cascada para que los usuarios siempre reciban su OTP aunque el canal principal falle.
Success dentro de una ventana de tiempo configurable (recomendado: 15 s para Verify Call).async function sendWithFallback(phone, otp) {
// 1. Try Verify Call first
try {
const flash = await fetch('.../call/flash', {
method: 'POST',
headers,
body: JSON.stringify({ callee: phone })
});
if (flash.ok) return { channel: 'flash', ...(await flash.json()) };
} catch {}
// 2. Flash failed — fall back to Telegram if user has it
try {
const tg = await fetch('.../telegram', {
method: 'POST',
headers,
body: JSON.stringify({ to: phone, code: otp, ttl: 120 })
});
if (tg.ok) return { channel: 'telegram', ...(await tg.json()) };
} catch {}
// 3. Final fallback: SMS (widest global reach)
const sms = await fetch('.../sms', {
method: 'POST',
headers,
body: JSON.stringify({ to: phone, text: `Your code: ${otp}` })
});
if (!sms.ok) throw new Error('All channels failed');
return { channel: 'sms', ...(await sms.json()) };
}Para deshabilitar la cascada en un flujo específico, omite simplemente los bloques try/catch de respaldo y muestra el error directamente al usuario. No existe una configuración de cascada del lado del servidor: la lógica reside completamente en tu código de integración.
Limitación de velocidad
Los límites de velocidad se aplican en la capa del gateway de API. Cuando se alcanza un límite, recibirás una respuesta 429 Too Many Requests. Comprueba el encabezado Retry-After para conocer el tiempo de espera exacto.
Retry-AfterX-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Resetconst res = await fetch(url, { method: 'POST', headers, body });
if (res.status === 429) {
const retryAfter = Number(res.headers.get('Retry-After') ?? 1);
console.warn(`Rate limited — waiting ${retryAfter}s`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
// retry the request
}Errores comunes de integración
Estos son los problemas más frecuentes reportados durante la integración. Consulta esta lista antes de abrir un ticket de soporte.