Gestion des erreurs
Toutes les réponses d'erreur suivent une structure JSON unique. Utilisez les codes de statut HTTP pour orienter votre logique de gestion des erreurs, et le champ error pour le contexte lisible par l'humain.
// Every non-2xx response returns:
{
"error": "human-readable description of what went wrong"
}Codes d'erreur
Le tableau ci-dessous couvre tous les codes de statut retournés par le Connect Hub. La colonne Réessayer indique si une nouvelle tentative automatique est sans risque.
| Code | Nom | Réessayer | Description |
|---|---|---|---|
400 | Bad Request | NO | Champs manquants ou invalides (par ex. téléphone non E.164, paramètre de corps obligatoire manquant). Corrigez votre charge utile. |
401 | Unauthorized | NO | En-tête x-api-key manquant ou invalide. Vérifiez votre clé API — ne réessayez pas automatiquement. |
402 | Payment Required | NO | Solde de compte insuffisant. Rechargez votre compte Novauth avant de réessayer. |
403 | Forbidden | NO | La clé API n'a pas la permission d'effectuer cette action. |
404 | Not Found | NO | La ressource n'existe pas (par ex. UUID inconnu sur /call/flash/{uuid}/cdr). Ne réessayez pas. |
409 | Conflict | NO | Une opération dupliquée ou conflictuelle a été détectée (par ex. signalement webhook deux fois pour le même UUID). |
422 | Unprocessable Entity | NO | Spécifique à Telegram : requête sémantiquement invalide (par ex. request_id expiré sur check-verification-status). |
429 | Too Many Requests | YES | Limite de débit dépassée. Respectez l'en-tête Retry-After et implémentez un recul exponentiel. |
480 | Temporarily Unavailable | YES | Destinataire temporairement inaccessible (dérivé de SIP). Sûr à relancer après un court délai. |
482 | Loop Detected | NO | Boucle de routage de requête détectée. Ne réessayez pas — investiguez votre intégration. |
486 | Busy Here | YES | Le destinataire est occupé. Réessayez après un délai ou basculez sur un autre canal. |
488 | Not Acceptable Here | NO | Paramètres de requête non acceptables (par ex. codec ou format non pris en charge). Corrigez la requête. |
500 | Internal Server Error | YES | Échec inattendu côté serveur. Réessayez avec un recul exponentiel (max 3 tentatives). |
502 | Bad Gateway | YES | Le service en amont (opérateur, passerelle) a retourné une réponse invalide. Réessayez après une courte attente. |
503 | Service Unavailable | YES | Service temporairement surchargé ou en maintenance. Respectez Retry-After si présent. |
603 | Decline | NO | Le destinataire a explicitement refusé (dérivé de SIP). Ne réessayez pas — l'utilisateur a activement rejeté l'appel. |
Logique de nouvelle tentative
Ne réessayez que sur les codes 429, 480, 486, 500, 502, 503. Utilisez un recul exponentiel en commençant à 500 ms (doublez à chaque tentative). Ne réessayez jamais automatiquement les erreurs client 4xx — corrigez d'abord la cause racine.
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' })
})
);Les opérations idempotentes (vérifications GET de statut) peuvent être relancées librement. Pour les opérations POST qui créent des ressources, vérifiez toujours si la ressource a déjà été créée avant de réessayer — utilisez l'UUID retourné pour interroger le statut d'abord.
Cascade de repli
Verify Call fonctionne dans la plupart des régions mais n'est pas universel. Implémentez une cascade afin que les utilisateurs reçoivent toujours leur OTP même en cas d'échec du canal principal.
Success dans une fenêtre de délai configurable (recommandé : 15 s pour 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()) };
}Pour désactiver la cascade pour un flux spécifique, omettez simplement les blocs try/catch de repli et affichez l'erreur directement à l'utilisateur. Il n'y a pas de paramètre de cascade côté serveur — la logique réside entièrement dans votre code d'intégration.
Limitation de débit
Les limites de débit sont appliquées au niveau de la passerelle API. Lorsqu'une limite est atteinte, vous recevez une réponse 429 Too Many Requests. Consultez l'en-tête Retry-After pour le temps d'attente exact.
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
}Erreurs d'intégration courantes
Ce sont les problèmes les plus fréquemment signalés lors de l'intégration. Consultez cette liste avant d'ouvrir un ticket de support.