Pereiti prie turinio
DikoAPI
API raktai

Įvykiai ir webhooks

Atverti Markdown

Įvykių tipai, parašo tikrinimas, pakartojimai, paslapties rotacija ir įvykių srautas.

Du būdai sužinoti apie pokyčius

Kad nereikėtų nuolat tikrinti būsenos, Diko siūlo du būdus:

  • Webhooks: Diko pati atsiunčia POST užklausą jūsų adresu, kai kas nors įvyksta.
  • Įvykių srautas: GET /events leidžia jums pasiimti įvykius, kada patogu. Tai ir atsarginis kelias, jei jūsų webhook endpointas buvo nepasiekiamas.

Abu būdai naudoja tą patį objektą event.

Endpointo kūrimas

  1. Atidarykite /settings/api/webhooks ir pasirinkite Sukurti endpointą. Patvirtinkite veiksmą pakartotiniu prisijungimu, jei Diko to paprašys.
  2. Įveskite HTTPS adresą ir pasirinkite įvykių tipus (arba visus).
  3. Nukopijuokite paslaptį whsec_.... Ji rodoma tik vieną kartą. Laikykite ją paslapčių saugykloje.
  4. Paspauskite Siųsti bandomąjį įvykį. Gausite webhook.test tik šiam endpointui; jis nepatenka į GET /events.

Vienai paskyrai galima turėti daugiausia 10 endpointų. Endpointas gali būti enabled, disabled arba failing_disabled (automatiškai išjungtas dėl nuolatinių nesėkmių, žr. žemiau). Diko rodo paskutinį sėkmingą ir nesėkmingą pristatymą, 30 dienų pristatymų žurnalą su visų bandymų istorija ir leidžia siųsti pristatymą dar kartą.

Įvykių katalogas

Visų to paties resurso įvykių data turi bendrą bazę: object ir id. Likusius laukus nurodo lentelė.

Tipas Kada siunčiamas Papildomi data laukai
meeting.ready susitikimo apdorojimas baigtas status, archived, minutes_status, minutes_version, run_id
meeting.failed apdorojimas nepavyko arba nutrūko status, archived, error_code, run_id
meeting.minutes.updated protokolas atnaujintas (pergeneravimas, dalyvio pervadinimas, gramatikos patvirtinimas) minutes_version
meeting.updated pakeistas pavadinimas arba susitikimas užarchyvuotas changed_fields (title, archived)
meeting.deleted susitikimas ištrintas –
library.transcript.added transkripcija pridėta į biblioteką youtube_video_id, source_type, added_at
library.transcript.updated santrauka tapo ready arba failed changed_fields (["summary"]), summary_status
library.transcript.removed transkripcija pašalinta iš bibliotekos –
library.import.succeeded importas baigtas transcript_id, reused
library.import.failed importas nepavyko error_code
library.import.canceled importas atšauktas –
webhook.test bandomasis įvykis, siunčiamas tik nurodytam endpointui ir nerodomas GET /events object (webhook_endpoint), id

meeting.ready gali pasikartoti, jei susitikimas apdorojamas iš naujo: tada run_id kitoks. Gali atsirasti naujų įvykių tipų, todėl nežinomus tipus ignoruokite.

Pristatymo formatas

Diko siunčia POST su JSON turiniu, kuris yra event objektas. Turinys baitas į baitą tas pats kiekviename bandyme.

JSON
{
  "id": "evt_3kQ9mXw0aB7nZp1RtYc4Ld",
  "object": "event",
  "type": "meeting.ready",
  "timestamp": "2026-10-11T09:30:00.000Z",
  "data": {
    "object": "meeting",
    "id": "mtg_0aZ1bY2cX3dW4eV5fU6gT7",
    "status": "ready",
    "archived": false,
    "minutes_status": "ready",
    "minutes_version": 2,
    "run_id": "5b0e8f1a-2c3d-4e5f-8a9b-1c2d3e4f5a6b"
  }
}

Diko laikosi Standard Webhooks 1.0 specifikacijos. Kiekviena užklausa turi šias antraštes:

Antraštė Reikšmė
webhook-id įvykio ID (evt_...), pastovus visiems bandymams
webhook-timestamp Unix sekundės kiekvieno bandymo pradžioje
webhook-signature v1,<base64 parašas>; rotacijos metu du parašai, atskirti tarpu
Content-Type application/json
User-Agent Diko-Webhooks/1.0 (+https://diko.app/en/api-docs/webhooks)

Parašo tikrinimas

Visada tikrinkite parašą. Kitaip bet kas, žinantis jūsų adresą, galėtų siųsti suklastotus įvykius.

  1. Perskaitykite neapdorotą užklausos turinį (baitus, ne iš naujo sugeneruotą JSON).
  2. Patikrinkite webhook-timestamp: skirtumas nuo dabartinio laiko turi būti ne didesnis kaip 5 minutės. Tai saugo nuo seno užklausos pakartojimo.
  3. Pasirašymo raktas yra dekoduoti base64 baitai tos eilutės dalies, kuri eina po whsec_.
  4. Pasirašomas tekstas: {webhook-id}.{webhook-timestamp}.{turinys}.
  5. Parašas yra base64(HMAC-SHA256(raktas, pasirašomas tekstas)).
  6. Palyginkite su kiekvienu v1,... parašu iš webhook-signature pastoviu laiku (constant-time). Pakanka, kad vienas sutaptų.
Python
import base64
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 5 * 60


def verify_webhook(secret: str, headers, raw_body: bytes) -> bool:
    """`secret` yra visa `whsec_...` eilutė, `headers` – nejautrus raidžių dydžiui žemėlapis."""
    try:
        message_id = headers["webhook-id"]
        timestamp = headers["webhook-timestamp"]
        signatures = headers["webhook-signature"]
        if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
            return False
    except (KeyError, ValueError):
        return False

    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{message_id}.{timestamp}.".encode() + raw_body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()

    for candidate in signatures.split(" "):
        version, _, signature = candidate.partition(",")
        # Lyginkite baitus: compare_digest meta TypeError, jei eilutėje yra ne ASCII simbolių.
        if version == "v1" and hmac.compare_digest(signature.encode(), expected.encode()):
            return True
    return False

Naudojimas su FastAPI ir Express:

Python
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()


@app.post("/diko")
async def diko_webhook(request: Request):
    raw_body = await request.body()
    if not verify_webhook(os.environ["DIKO_WEBHOOK_SECRET"], request.headers, raw_body):
        raise HTTPException(status_code=400, detail="invalid signature")
    event = json.loads(raw_body)
    # Įdėkite į eilę, pašalinkite dublius pagal event["id"] ir atsakykite greitai.
    return {"ok": True}

Pristatymo garantijos

  • Bent kartą (at-least-once). Tas pats įvykis gali atkeliauti kelis kartus. Šalinkite dublius pagal webhook-id (tai tas pats kaip event.id).
  • Eilės tvarka negarantuojama. Dėl pakartojimų senesnis įvykis gali atkeliauti vėliau. Be to, Diko siunčia iki dviejų to paties endpointo įvykių vienu metu, todėl net be pakartojimų du įvykiai gali atkeliauti atvirkščiai. Vertinkite timestamp arba perskaitykite resurso dabartinę būseną per API. GET /events yra vienintelė vieta, kur įvykiai išdėstyti tvarka (pagal užfiksavimą).
  • Atsakykite greitai. Pristatymas laikomas sėkmingu tik gavus 2xx. Atsakymo laukiama ne ilgiau kaip 15 sekundžių, todėl įvykį pirmiausia įdėkite į eilę, o sunkų darbą atlikite vėliau.

Pakartojimai

Kartojami: kiekvienas ne-2xx atsakymas, pasibaigęs skirtasis laikas (15 s) ir tinklo klaida. Iš viso daroma 8 bandymai per maždaug 41 valandą:

Bandymas 1 2 3 4 5 6 7 8
Kada iškart po 1 min. po 5 min. po 30 min. po 2 val. po 5 val. po 10 val. po 24 val.

Intervalai keičiami atsitiktine ±20 % paklaida. Kiekvienas bandymas gauna naują webhook-timestamp, todėl pakartojimas po 10 minučių vis tiek praeina 5 minučių tikrinimą. Turinys ir webhook-id nesikeičia.

  • Diko atsižvelgia į antraštę Retry-After (iki 1 valandos).
  • Diko neina pagal peradresavimus (3xx); jie laikomi nesėkme.
  • 410 Gone reiškia „šio endpointo daugiau nėra“: Diko išjungia endpointą (disabled), atšaukia laukiančius pristatymus ir išsiunčia jums el. laišką.

Grandinės pertraukiklis ir automatinis išjungimas

Po 5 nesėkmių iš eilės Diko sustabdo siuntimą 1 minutei, o toliau pauzę dvigubina iki 2 valandų. Pasibaigus pauzei išsiunčiamas vienas bandomasis pristatymas. Jei endpointas nepavyksta ilgiau nei 72 valandas, jis tampa failing_disabled ir gaunate el. laišką (jame endpointas įvardijamas tik domenu ir ID; pilnas adresas lieka programėlėje).

Kai endpointą vėl įjungsite, nepavykusius pristatymus galite pakartoti programėlėje arba pasiimti praleistus įvykius iš GET /events.

Kai endpointą išjungiate jūs patys, jau laukiantys pristatymai lieka laukti ir išsiunčiami jį vėl įjungus. Kai po 72 valandų nesėkmių jį išjungia Diko, laukiantys pristatymai pažymimi kaip nepavykę, kad Pakartoti nepavykusius galėtų juos išsiųsti.

Sulaikyti pristatymai

Jei jūsų API prieiga sustabdyta arba planas nebeapima API, Diko pristatymus sulaiko: nieko nesiunčia ir nenaudoja bandymų. Patys įvykiai vis tiek įrašomi. Kol prieiga sustabdyta, pristatymai laukia neišsiųsti. Atšaukus sustabdymą, jie išsiunčiami maždaug per minutę. Jei planas nebeapima API, Diko iš naujo patikrina sulaikytus pristatymus po 15 minučių, o paskui vis rečiau. Laukimas ilgėja kartu su pristatymo amžiumi, iki 6 valandų. Kai planas vėl leidžia API, pristatymai išsiunčiami. Taip sulaikytas ilgiau nei 7 dienas pristatymas baigiasi kaip failed su plan_required. Atkūrę prieigą, galite jį išsiųsti iš pristatymų žurnalo mygtukais Siųsti dar kartą arba Pakartoti nepavykusius.

Paslapties rotacija

Rotuokite paslaptį puslapyje /settings/api/webhooks mygtuku Rotuoti paslaptį (reikia pakartotinio patvirtinimo). Nauja paslaptis rodoma vieną kartą, o sena galioja dar 24 valandas. Per tą laiką kiekvienas pristatymas turi du parašus antraštėje webhook-signature, atskirtus tarpu. Aukščiau pateiktas kodas tikrina visus, todėl:

  1. Sukurkite naują paslaptį.
  2. Įdiekite naują paslaptį ten, kur priimate webhooks.
  3. Palaukite, kol sena nustos galioti, arba patikrinkite, ar jūsų kodas jau naudoja naują.

Bandymas, pakartojimas ir žurnalas

Programėlėje galite: siųsti bandomąjį įvykį, peržiūrėti 30 dienų pristatymų žurnalą su kiekvieno bandymo būsena, pakartoti vieną pristatymą mygtuku Siųsti dar kartą ir Pakartoti nepavykusius už pasirinktą laikotarpį (iki 1 000 pristatymų). Bandomasis įvykis, pakartojimas ir masinis pakartojimas vienam endpointui ribojami iki 6 per minutę.

  • Bandomasis įvykis (webhook.test) gauna vieną bandymą be pakartojimų. Jis pristatomas net išjungtam endpointui, o nesėkmė neįskaitoma į grandinės pertraukiklį (sėkmė jį vis tiek atstato).
  • Pakartojant pristatymą, kurio 8 bandymai išnaudoti, jis išsiunčiamas dar lygiai vieną kartą; jei nepavyksta, lieka nepavykęs.

URL taisyklės ir SSRF apsauga

Diko siunčia užklausas iš savo serverių, todėl adresui taikomos griežtos taisyklės:

  • tik https, prievadas 443, be vartotojo vardo ir slaptažodžio URL;
  • negalima naudoti IP adreso: reikia domeno vardo su tašku;
  • ne ilgesnis kaip 2048 simboliai;
  • kiekvieno bandymo metu domenas išsprendžiamas iš naujo ir visi adresai turi būti vieši. Privatūs, vietiniai (loopback), link-local, multicast ir rezervuoti adresai atmetami, įskaitant į kitus formatus įterptus IPv4 (IPv4-mapped, 6to4, Teredo, NAT64);
  • draudžiami hostai localhost, *.local, metadata.google.internal ir apskritai vidiniai serverių vardai (pavyzdžiui, *.internal);
  • jungtis prisegama prie patikrinto IP, TLS sertifikatas turi būti galiojantis, Diko neina pagal peradresavimus, o atsakymas skaitomas ne daugiau kaip 64 KB.

Įvykių srautas kaip alternatyva

GET /events leidžia pasiimti įvykius patiems. Tai tinka, jei negalite atverti viešo endpointo arba norite patikrinti, ar nepraleidote įvykių po prastovos. Pirmą kartą pradėkite nuo cursor=latest, o paskui saugokite paskutinį next_cursor. Diko užfiksuoja įvykius tik tada, kai paskyra turi neatšauktą raktą arba įjungtą webhook endpointą. Anksčiau pasibaigę susitikimai įvykių nesukuria, todėl pirmas paleidimas jų neatkuria:

Python
import os
import time
from pathlib import Path

import requests

BASE = "https://api.diko.app/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DIKO_API_KEY']}"}
CURSOR_FILE = Path("diko-events.cursor")  # laikykite ten, kur jūsų paslauga gali rašyti


def load_cursor():
    return CURSOR_FILE.read_text().strip() if CURSOR_FILE.exists() else None


def save_cursor(cursor):
    CURSOR_FILE.write_text(cursor)


def handle(event):
    print(event["type"], event["id"])  # pakeiskite savo apdorojimu


cursor = load_cursor() or "latest"  # išsaugotas žymeklis arba „nuo dabar“
while True:
    response = requests.get(
        f"{BASE}/events",
        headers=HEADERS,
        params={"cursor": cursor, "limit": 100, "types": ["meeting.ready"]},
        timeout=30,
    )
    response.raise_for_status()
    page = response.json()
    for event in page["data"]:
        handle(event)  # idempotentiškai: dublius šalinkite pagal event["id"]
    cursor = page["next_cursor"]
    save_cursor(cursor)
    if not page["has_more"]:
        time.sleep(10)

Rodomi tik užfiksuoti įvykiai, išdėstyti užfiksavimo tvarka, todėl nė vienas nepraleidžiamas. Raktas mato tik tų šeimų įvykius, kuriuos gali skaityti pagal savo leidimus.

Ieškokite temos, endpointo ar klaidos kodo.

Paieška visuose dokumentacijos puslapiuoseesc uždaryti