APIPraćenje

Webhookovi

Događaji u vaš sistem čim se dese: potpisani, s ponovnim pokušajima i logom isporuka.

Webhook je HTTPS adresa vašeg sistema na koju Cifra šalje POST s događajem. Dodajte je na platformi (Razvoj → Webhookovi) ili kroz API, odaberite događaje i sačuvajte tajnu (whsec_…) kojom provjeravate potpis.

curl -X POST "https://api.cifra.ba/v1/webhooks" \
  -H "Authorization: Bearer $CIFRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.firma.ba/cifra",
    "events": [
      "watchlist.alert",
      "watchlist.report_ready"
    ],
    "description": "Produkcija — kreditni sistem"
  }'

Događaji

watchlist.alertAPIAlarm za praćeno vozilo. Pravilo vozila se aktiviralo: vrijednost se promijenila, prešla ciljnu cijenu ili LTV prag, ili je stigao povoljan oglas.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "watchlist.alert",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "alert": {
      "object": "alert",
      "id": "alr_8fK2mQ9xLp4TvB7nR1sW",
      "type": "value_changed",
      "watched_vehicle": {
        "id": "wv_3JdP0aZr6YcN2uE5kHq8",
        "label": "VW Golf 1.6 TDI — ugovor 2024/118",
        "external_ref": "2024/118",
        "kind": "owned"
      },
      "day": "2026-10-15",
      "previous_value": 21400,
      "value": 20550,
      "change_pct": -3.97,
      "threshold": 3,
      "details": null,
      "created_at": "2026-10-15T06:12:44.000Z"
    }
  }
}
watchlist.report_readyAPIDnevni izvještaj spreman. Jutarnja procjena svih praćenih vozila je gotova; izvještaj dana čita se jednim pozivom.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "watchlist.report_ready",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "day": "2026-10-15",
    "vehicles": 412,
    "alerts": 7,
    "report_url": "/v1/watchlist/report?day=2026-10-15"
  }
}
widget.rating_changedAPIPromijenjena ocjena cijene. Jutarnja procjena je pomjerila ocjenu tražene cijene na vašem widgetu (npr. iz „Fer” u „Povišena”), ili je vozilo iz oglasa prodano.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "widget.rating_changed",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "widget": {
      "object": "widget",
      "id": "wgt_7Hq2Lm9PzX4cV1bN8sKd",
      "public_id": "k3M9xQ2vTa",
      "external_ref": "LAG-0412",
      "title": "Volkswagen Golf 7 1.6 TDI Highline",
      "status": "active",
      "asking_price": 23900,
      "value": 22950,
      "previous_level": "fair",
      "level": "high",
      "pct": 4.14,
      "day": "2026-10-15"
    }
  }
}
report.createdplatformaNova procjena na platformi. Nova procjena vozila na platformi: vrijednost, raspon, svrha i vaša referenca, da procjena odmah stigne u vaš sistem.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "report.created",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "report": {
      "object": "report",
      "id": "cm2x8k1q00001",
      "url": "https://enterprise.cifra.ba/valuations/cm2x8k1q00001",
      "purpose": "financing",
      "reference": "2024/118",
      "vehicle": {
        "vehicle_type": "car",
        "make": "volkswagen",
        "model": "golf",
        "make_name": "Volkswagen",
        "model_name": "Golf",
        "year": 2019,
        "mileage_km": 98000,
        "fuel": "diesel",
        "transmission": "manual",
        "power_kw": 85
      },
      "value": 20550,
      "low": 19300,
      "high": 21900,
      "currency": "BAM",
      "grade": "A",
      "as_of": "2026-10-15",
      "created_by": {
        "name": "Amra Hodžić",
        "email": "amra@firma.ba"
      },
      "created_at": "2026-10-15T09:41:12.000Z"
    }
  }
}
check.completedplatformaProvjera vozila završena. Provjera oglasa je gotova (nova ili ponovljena): nivo pažnje i signali koje treba provjeriti.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "check.completed",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "check": {
      "object": "vehicle_check",
      "id": "cm2x9a7d00004",
      "url": "https://enterprise.cifra.ba/check/cm2x9a7d00004",
      "vehicle": "Volkswagen Golf 2016",
      "listing_url": "https://olx.ba/artikal/61234567",
      "level": "elevated",
      "headline": "Kilometraža niža nego u ranijem oglasu",
      "signals": [
        {
          "code": "mileage_rollback",
          "severity": "high"
        },
        {
          "code": "relisted_often",
          "severity": "medium"
        }
      ],
      "purpose": "financing",
      "reference": "2024/118",
      "created_by": {
        "name": "Amra Hodžić",
        "email": "amra@firma.ba"
      },
      "checked_at": "2026-10-15T09:52:03.000Z"
    }
  }
}
check.decidedplatformaOdluka o provjeri. Odluka tima o provjerenom vozilu: odobreno, dodatna provjera ili odbijeno, s napomenom.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "check.decided",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "check": {
      "object": "vehicle_check",
      "id": "cm2x9a7d00004",
      "url": "https://enterprise.cifra.ba/check/cm2x9a7d00004",
      "vehicle": "Volkswagen Golf 2016",
      "level": "elevated",
      "reference": "2024/118",
      "decision": "review",
      "decision_note": "Tražiti servisnu knjižicu prije odobrenja.",
      "decided_by": {
        "name": "Emir Kovač",
        "email": "emir@firma.ba"
      },
      "decided_at": "2026-10-15T10:05:27.000Z"
    }
  }
}
bulk.completedplatformaMasovna procjena gotova. Svi redovi liste su procijenjeni: koliko vozila, ukupna vrijednost i veza na listu.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "bulk.completed",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "batch": {
      "object": "valuation_batch",
      "id": "cm2xa0b3k0009",
      "url": "https://enterprise.cifra.ba/valuations/bulk/cm2xa0b3k0009",
      "name": "Lizing portfelj Q3",
      "purpose": "financing",
      "as_of": "2026-09-30",
      "rows": 1240,
      "valued": 1228,
      "failed": 12,
      "total_value": 24118400,
      "currency": "BAM",
      "created_by": {
        "name": "Amra Hodžić",
        "email": "amra@firma.ba"
      },
      "completed_at": "2026-10-15T11:20:45.000Z"
    }
  }
}
usage.threshold_reachedAPIPotrošnja API-ja prešla prag. Ovomjesečne obračunske jedinice su prešle prag koji ste odabrali (npr. 80% ugovorene kvote).
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "usage.threshold_reached",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "month": "2026-10",
    "threshold_pct": 80,
    "units": 80214,
    "quota": 100000,
    "resets_at": "2026-11-01T00:00:00.000Z"
  }
}
webhook.testplatformaTest. Probna poruka koju šaljete sami, da provjerite da adresa prima i provjerava potpis.
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "webhook.test",
  "created_at": "2026-10-15T06:12:44.000Z",
  "livemode": true,
  "data": {
    "message": "A test event from Cifra."
  }
}

Zahtjev koji stiže

ZaglavljeVrijednost
Cifra-Signaturet=<unix vrijeme>,v1=<HMAC-SHA256>; nakon zamjene tajne dva v1
Cifra-Event-IdOznaka događaja (evt_…), ista u svakom ponovnom pokušaju
Cifra-Event-Typenpr. watchlist.alert
User-AgentCifra-Webhooks/1.0
Tijelo
{
  "id": "evt_2kQ9mX7vR4tL8bN1cZ5p",
  "type": "watchlist.alert",
  "created_at": "2026-12-02T06:12:44.000Z",
  "livemode": true,
  "data": {
    "alert": {
      "object": "alert",
      "id": "alr_8fK2mQ9xLp4TvB7nR1sW",
      "type": "exposure_above",
      "watched_vehicle": {
        "id": "wv_3JdP0aZr6YcN2uE5kHq8",
        "label": "VW Golf 7 — ugovor 2024/118",
        "external_ref": "2024/118",
        "kind": "owned"
      },
      "day": "2026-12-02",
      "previous_value": 22350,
      "value": 21400,
      "change_pct": -4.25,
      "threshold": 85,
      "details": {
        "exposure": {
          "kind": "financing",
          "amount": 18259,
          "ratio": 0.8532
        }
      },
      "created_at": "2026-12-02T06:12:44.000Z"
    }
  }
}

Provjera potpisa

Potpis je HMAC-SHA256 nad "<t>.<tijelo>" vašom tajnom, heksadecimalno. Provjerite ga nad sirovim tijelom (prije parsiranja JSON-a), odbijte zahtjeve starije od 5 minuta i uporedite u konstantnom vremenu. Nakon zamjene tajne Cifra neko vrijeme potpisuje i starom (drugi v1): prihvatite ako odgovara bilo koji.

import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.CIFRA_WEBHOOK_SECRET;
const app = express();

// The raw body: the signature is over the bytes as sent.
app.post("/cifra", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Cifra-Signature") ?? "";
  const t = Number(header.match(/t=(\d+)/)?.[1]);
  const given = [...header.matchAll(/v1=([0-9a-f]+)/g)].map((m) => m[1]);
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - t) < 300;
  const valid = given.some(
    (v) => v.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected)),
  );
  if (!fresh || !valid) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // A retried event carries the same id: process each id once.
  queue.push(event);
  res.sendStatus(200);
});

Odgovor i ponovni pokušaji

Odgovorite 2xx u roku od 10 sekundi — najbolje odmah, a događaj obradite u pozadini. Sve ostalo (drugi status, istek vremena, nedostupna adresa) je neuspjeh i Cifra pokušava ponovo:

PokušajKadUkupno
1.odmah—
2.1 min nakon prethodnog~1 min od prvog
3.5 min nakon prethodnog~6 min od prvog
4.30 min nakon prethodnog~36 min od prvog
5.2 h nakon prethodnog~2.6 h od prvog
6.6 h nakon prethodnog~8.6 h od prvog
7.12 h nakon prethodnog~20.6 h od prvog
8.1 dan nakon prethodnog~1.8583333333333334 dan od prvog
  • Nakon zadnjeg pokušaja isporuka je failed; vidi se u isporukama i može se ponovo poslati s platforme.
  • Ako sve isporuke na adresu padaju 1 sat, administratori kompanije dobiju email.
  • Nakon 3 dana samo neuspjeha adresa se isključuje (disabled), da ne gomila događaje; uključuje se na platformi.
  • Redoslijed nije zagarantovan: koristite created_at događaja, ne redoslijed dolaska.

Isprobavanje

POST /v1/webhooks/{id}/test šalje webhook.test odmah, a isporuke pokazuju status, HTTP odgovor vaše adrese i grešku. Za lokalni razvoj koristite tunel (npr. ngrok) — adresa mora biti javna i HTTPS.

Ograničenja

  • Najviše 5 aktivnih adresa po okruženju (live i test odvojeno).
  • Samo HTTPS na javne adrese; lokalne i privatne mreže se odbijaju, i pri dodavanju i pri svakoj isporuci.
  • Zamjena tajne je na platformi: stara potpisuje još do 7 dana, po izboru.