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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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).
{
"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.
{
"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
| Zaglavlje | Vrijednost |
|---|---|
Cifra-Signature | t=<unix vrijeme>,v1=<HMAC-SHA256>; nakon zamjene tajne dva v1 |
Cifra-Event-Id | Oznaka događaja (evt_…), ista u svakom ponovnom pokušaju |
Cifra-Event-Type | npr. watchlist.alert |
User-Agent | Cifra-Webhooks/1.0 |
{
"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šaj | Kad | Ukupno |
|---|---|---|
| 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_atdogađ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.