← Înapoi la documentația API

Webhooks

isahar Voice îți poate trimite, proactiv, o notificare HTTP POST către un URL al tău, la evenimente din cont. Configurare: Integrări → Webhooks, din panoul de administrare.

1. Evenimente disponibile

EvenimentCând se trimite
call.createdUn apel nou (intrat sau ieșit) a fost înregistrat — la începutul apelului.
call.endedApelul s-a terminat, indiferent de rezultat (status-ul final e în data.status).
call.missedÎn plus față de call.ended, doar când un apel intrat s-a terminat MISSED.
recording.readyÎnregistrarea apelului a fost salvată și e disponibilă (nu se trimite un URL de fișier — doar id-ul apelului).

2. Structura comună a payload-ului

Fiecare livrare are exact aceste câmpuri, plus data, specific fiecărui eveniment:

{
  "id": "evt_9f1c2a3b4d5e6f708192a3b4",
  "type": "call.created",
  "version": "1",
  "created_at": "2026-01-15T10:00:00.000Z",
  "data": { "callId": "c1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8", "direction": "INBOUND", "from": "+40711111111", "to": "+40788800000" }
}

Același id ajunge pe toate URL-urile tale active pentru acel eveniment, dacă ai mai multe webhook-uri configurate — util pentru deduplicare la nivel de eveniment (fiecare încercare de livrare are propriul ei identificator intern, separat).

Exemple complete pentru fiecare eveniment: vezi /docs/openapi.json, obiectul webhooks (standard OpenAPI 3.1) — schemele exacte sunt în components.schemas.CallCreatedEvent, CallEndedEvent, CallMissedEvent, RecordingReadyEvent.

3. Semnătura — header X-Isahar-Signature

Fiecare livrare are header-ul:

X-Isahar-Signature: t=1700000000,v1=5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d1

t = timestamp Unix (secunde) la momentul semnării. v1 = HMAC-SHA256 în hex, calculat exact așa:

hmac_sha256(secret, `${t}.${corpul_brut_al_cererii}`)

Important: semnătura se calculează peste corpul brut al cererii (bytes exacți, așa cum au fost trimiși), nu peste un JSON re-serializat de tine după parsare — cele două pot diferi (ordinea câmpurilor, spații) și semnătura nu se mai potrivește. Capturează corpul brut înainte de orice parsare JSON.

Secretul webhook-ului se arată o singură dată, la creare, în panoul Integrări → Webhooks.

Protecție anti-replay

Timestamp-ul (t) este parte din ce se semnează — asta îți permite să respingi livrări cu un t prea vechi (o cerere capturată și retrimisă mai târziu ar avea o semnătură validă, dar un timestamp expirat). isahar Voice semnează cu timestamp-ul curent la fiecare livrare (inclusiv reîncercări); verificarea vechimii lui t este responsabilitatea aplicației tale — recomandăm respingerea semnăturilor cu t mai vechi de 5 minute.

Verificare — Node.js

const crypto = require('crypto');

function verifyIsaharSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
  const timestamp = parts.t;
  const provided = parts.v1;
  if (!timestamp || !provided) return false;

  // Anti-replay -- respinge semnaturi mai vechi de 5 minute.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(provided, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: foloseste express.raw() pe ruta asta, ca sa ai corpul BRUT (Buffer), nu unul
// deja parsat de un middleware JSON global.
app.post(
  '/webhooks/isahar',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const rawBody = req.body.toString('utf8');
    const signature = req.headers['x-isahar-signature'];
    if (!signature || !verifyIsaharSignature(rawBody, signature, process.env.ISAHAR_WEBHOOK_SECRET)) {
      return res.status(401).send('invalid signature');
    }
    const event = JSON.parse(rawBody);
    // ... proceseaza event.type / event.data ...
    res.status(200).send('ok');
  },
);

Verificare — PHP

<?php

function verifyIsaharSignature(string $rawBody, string $signatureHeader, string $secret): bool {
    $parts = [];
    foreach (explode(',', $signatureHeader) as $pair) {
        $kv = explode('=', $pair, 2);
        if (count($kv) === 2) {
            $parts[$kv[0]] = $kv[1];
        }
    }
    $timestamp = $parts['t'] ?? null;
    $provided = $parts['v1'] ?? null;
    if ($timestamp === null || $provided === null) {
        return false;
    }

    // Anti-replay -- respinge semnaturi mai vechi de 5 minute.
    if (abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $provided);
}

$rawBody = file_get_contents('php://input');
$signatureHeader = $_SERVER['HTTP_X_ISAHAR_SIGNATURE'] ?? '';
$secret = getenv('ISAHAR_WEBHOOK_SECRET');

if (!verifyIsaharSignature($rawBody, $signatureHeader, $secret)) {
    http_response_code(401);
    exit('invalid signature');
}

$event = json_decode($rawBody, true);
// ... proceseaza $event['type'] / $event['data'] ...
http_response_code(200);
echo 'ok';

4. Ce trebuie să răspundă endpoint-ul tău

Orice status HTTP 2xx înseamnă livrare confirmată. Orice alt status, sau lipsa unui răspuns în 10 secunde, contează ca eșec — indiferent dacă tu ai procesat corect evenimentul intern, dar ai răspuns cu alt cod.

5. Reîncercări și dezactivare automată

La eșec, isahar Voice reîncearcă automat, cu intervale progresive, până la 5 încercări în total (livrarea inițială + 4 reîncercări):

ÎncercareAșteptare până la următoarea (dacă eșuează)
1 (livrarea inițială)1 minut
25 minute
315 minute
430 minute
5—

Dacă și a 5-a încercare eșuează, webhook-ul este dezactivat automat (rămâne configurat, dar nu mai primește livrări) și toți administratorii tenantului primesc un email de notificare. Un webhook dezactivat se reactivează manual din panou, după ce verifici URL-ul.

Un succes (orice 2xx) resetează complet contorul de eșecuri — nu se acumulează între evenimente diferite.

6. Administrare din panou

Din Integrări → Webhooks (panoul de administrare, autentificare separată de cheile API — nu prin API-ul public):

Acestea sunt funcții ale panoului de administrare, nu endpoint-uri ale API-ului public — autentificarea se face cu contul tău isahar Voice, nu cu o cheie API.