Rukovanje greškama
Svi odgovori sa greškom prate jedinstvenu JSON strukturu. Koristite HTTP statusne kodove za usmeravanje logike obrade grešaka, a polje error za kontekst čitljiv ljudima.
// Every non-2xx response returns:
{
"error": "human-readable description of what went wrong"
}Kodovi grešaka
Tabela u nastavku pokriva sve statusne kodove koje vraća Connect Hub. Kolona Ponovni pokušaj pokazuje da li je automatsko ponavljanje bezbedno.
| Kod | Naziv | Ponovni pokušaj | Opis |
|---|---|---|---|
400 | Bad Request | NO | Nedostajuća ili nevažeća polja (npr. telefon nije u E.164 formatu, nedostaje obavezan parametar tela). Ispravite svoj payload. |
401 | Unauthorized | NO | Nedostajuće ili nevažeće x-api-key zaglavlje. Proverite svoj API ključ — ne ponavljajte automatski. |
402 | Payment Required | NO | Nedovoljno stanje na nalogu. Dopunite svoj Novauth nalog pre ponovnog pokušaja. |
403 | Forbidden | NO | API ključ nema dozvolu za izvršavanje ove akcije. |
404 | Not Found | NO | Resurs ne postoji (npr. nepoznat UUID na /call/flash/{uuid}/cdr). Ne pokušavajte ponovo. |
409 | Conflict | NO | Otkrivena je duplirana ili konfliktna operacija (npr. dvostruko prijavljivanje webhook-a za isti UUID). |
422 | Unprocessable Entity | NO | Specifično za Telegram: zahtev je semantički nevažeći (npr. istekao request_id na check-verification-status). |
429 | Too Many Requests | YES | Prekoračen limit broja zahteva. Poštujte zaglavlje Retry-After i implementirajte eksponencijalno odlaganje. |
480 | Temporarily Unavailable | YES | Primalac je privremeno nedostupan (izvedeno iz SIP-a). Bezbedno je pokušati ponovo nakon kraće pauze. |
482 | Loop Detected | NO | Otkrivena je petlja u rutiranju zahteva. Ne pokušavajte ponovo — istražite svoju integraciju. |
486 | Busy Here | YES | Primalac je zauzet. Pokušajte ponovo nakon pauze ili pređite na drugi kanal. |
488 | Not Acceptable Here | NO | Parametri zahteva nisu prihvatljivi (npr. nepodržan kodek ili format). Ispravite zahtev. |
500 | Internal Server Error | YES | Neočekivani otkaz na serverskoj strani. Pokušajte ponovo uz eksponencijalno odlaganje (maks. 3 pokušaja). |
502 | Bad Gateway | YES | Uzvodni servis (operater, gateway) vratio je nevažeći odgovor. Pokušajte ponovo nakon kraćeg čekanja. |
503 | Service Unavailable | YES | Servis je privremeno preopterećen ili na održavanju. Poštujte Retry-After ako je prisutan. |
603 | Decline | NO | Primalac je izričito odbio poziv (izvedeno iz SIP-a). Ne pokušavajte ponovo — korisnik je aktivno odbio poziv. |
Logika ponavljanja
Ponavljajte samo kodove 429, 480, 486, 500, 502, 503. Koristite eksponencijalno odlaganje počevši od 500 ms (udvostručite pri svakom pokušaju). Nikada ne ponavljajte automatski 4xx klijentske greške — prvo otklonite osnovni uzrok.
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' })
})
);Idempotentne operacije (GET provere statusa) mogu se slobodno ponavljati. Za POST operacije koje kreiraju resurse, uvek proverite da li je resurs već kreiran pre ponavljanja — koristite vraćeni UUID da prvo proverite status.
Rezervna kaskada
Verify Call radi u većini regiona, ali nije univerzalan. Implementirajte kaskadu tako da korisnici uvek dobiju svoj OTP čak i kada primarni kanal zakaže.
Success u podesivom vremenskom okviru (preporučeno: 15 s za 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()) };
}Da biste onemogućili kaskadu za određeni tok, jednostavno izostavite rezervne try/catch blokove i prikažite grešku direktno korisniku. Ne postoji podešavanje kaskade na serverskoj strani — logika u potpunosti živi u vašem integracionom kodu.
Ograničavanje broja zahteva
Ograničenja broja zahteva primenjuju se na sloju API gateway-a. Kada se dostigne limit, dobijate odgovor 429 Too Many Requests. Proverite zaglavlje Retry-After za tačno vreme čekanja.
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
}Česte greške pri integraciji
Ovo su najčešći problemi prijavljeni tokom integracije. Proverite ovu listu pre otvaranja tiketa podrške.