# Rapportly-webhooks zelfstandig aansluiten

Deze handleiding beschrijft de beschikbare API binnen de vermelde releasegrenzen. Actuele sleutel-, gebruikers-, pakket- en objectrechten blijven vereist. Niet alle websitefuncties zijn via de API beschikbaar.

Een webhook is een melding die Rapportly naar jouw server stuurt wanneer een verwerking klaar is, definitief mislukt of een gekoppelde mail van toestand verandert. Je hoeft dan niet voortdurend te vragen of de verwerking al klaar is. Het nieuwe API-kanaal stuurt technische verwijzingen; haal de inhoud daarna op via de gewone API met passende leesrechten. De bestaande Zapier-koppeling heeft een eigen berichtformaat.

## 1. Bereid je eigen omgeving voor

- Gebruik een productie-API-sleutel met `webhooks:beheren` én de rechten voor de verwerkingen die je wilt starten. Abonnement en verwerking moeten bij dezelfde sleutel horen. Een beheerder kan de sleutel voor het teamlid klaarzetten en diens rechten begrenzen.
- Maak een publiek HTTPS-endpoint op je eigen server. Geen IP-adres als host, interne bestemming, gebruikersnaam/wachtwoord in de URL of redirect. Bewaar geheime waarden in je serverkluis.
- Laat je server de **ruwe bodybytes** tot maximaal 32 KiB lezen vóór JSON-parsing. Bewaar een ontvangen event-id duurzaam en uniek. Verwerk een dubbel event niet opnieuw.
- Gebruik eerst fictieve gegevens en een apart abonnement. De fictieve API-testmodus verstuurt geen echte webhook. Deze aansluiting gebruikt de productie-API; het starten van een echte verwerking kan credits kosten.

## 2. Maak het abonnement

Basisadres: `https://api.rapportly.io/api/rapportly/v1`.

```http
POST /api/rapportly/v1/webhooks
Authorization: Bearer <jouw_productiesleutel>
Idempotency-Key: crm-webhook-abonnement-0001
Content-Type: application/json

{
  "url": "https://jouw-eigen-domein.nl/rapportly-meldingen",
  "events": ["verwerking.gereed", "verwerking.mislukt"],
  "bevestigd": true
}
```

Het antwoord is 201 met `id`, `url`, `events`, `actief`, `aangemaakt_op`, `signing_secret` en `herhaald:false`. Bewaar het signingsecret veilig bij dit abonnement. Je krijgt het niet via de lijst terug. Bij een verloren antwoord herhaal je exact dezelfde body en Idempotency-Key:200, hetzelfde abonnement en signingsecret, `herhaald:true`. Andere invoer met die code geeft 409. Een ingetrokken abonnement wordt niet opnieuw geactiveerd:410.

Maximaal 20 actieve abonnementen per sleutel. De URL en events zijn onveranderlijk. Voor een wijziging maak je een nieuw abonnement en trek je het oude in. Gebruik een nieuwe aanmaakcode. Behandel `signing_secret` als wachtwoord; zet het niet in logs, browsercode, tickets of voorbeelden.

## 3. Controleer iedere ontvangen melding

Rapportly verstuurt vier headers:

- `X-Rapportly-Event`: één van de zes eventtypes uit dit document.
- `X-Rapportly-Event-Id`: vaste event-UUID, ook bij herhaling.
- `X-Rapportly-Timestamp`: Unix-tijd in seconden, opnieuw gezet per poging.
- `X-Rapportly-Signature`: `v1=` gevolgd door de hexadecimale HMAC-SHA256.

De HMAC-invoer is `timestamp + '.' + event_id + '.' + exacte ruwe bodybytes`. Gebruik het signingsecret van dit abonnement. Accepteer maximaal vijf minuten tijdsverschil en vergelijk handtekeningen op een manier die geen teken-voor-teken timing prijsgeeft. Controleer daarna of event-id en type in de body bij de headers horen. Houd de serverklok actueel.

Het bestand `rapportly-webhook-verify.mjs` bevat een Node.js-controlefunctie. Het is geen complete server. Ongeldige invoer geeft `null`; geldige invoer geeft het JSON-event. Bewaar vervolgens in **één database-transactie** het unieke event-id en een taak voor jouw eigen verwerking. Bestaat dat event-id al, maak geen tweede taak. Antwoord daarna snel met200, 202 of 204. Doe langdurig werk in je eigen wachtrij. Zo kan een crash niet leiden tot een bevestigde maar vergeten opdracht.

Voorbeeld van de body:

```json
{
  "id": "00000000-0000-4000-8000-000000000001",
  "type": "verwerking.gereed",
  "created_at": "2026-10-04T12:00:00Z",
  "data": {
    "run_id": "00000000-0000-4000-8000-000000000002",
    "job_id": "00000000-0000-4000-8000-000000000003",
    "actie": "uitvoer.hergenereren",
    "object_soort": "gesprek",
    "object_id": "00000000-0000-4000-8000-000000000004"
  }
}
```

Er staan geen klantnamen, transcripties, prompts, mailadressen, modelantwoorden of downloadlinks in de melding. De negen aangesloten jobtypen zijn rapportgeneratie (ook offertes/presentaties), gespreksgeneratie, rapportbijsturing, gespreksbijsturing, audiotranscriptie, klantbriefing, moderne stijltest, klassieke stijlverfijning en stijlwerkplaatsvoorstel. Een tussentijdse fout die Rapportly nog herstelt geeft geen definitieve mislukkingsevent.

## 3a. Herken de vier mailmeldingen

Je kunt één tot zes unieke events kiezen: de twee verwerkingsmeldingen hierboven en `mail.geaccepteerd`, `mail.onzeker`, `mail.mislukt`, `mail.afgeleverd`. Kies alleen wat jouw ontvanger verwerkt. Mailmeldingen horen bij de oorspronkelijke, via dezelfde API-sleutel goedgekeurde outbox. Bestaande native mails worden niet automatisch overgenomen.

| Event | Wat het bewijst |
|---|---|
| `mail.geaccepteerd` | De verzendprovider heeft de betreffende poging geaccepteerd. Dit bewijst geen aflevering bij de ontvanger. |
| `mail.onzeker` | De poging staat nog als onzeker geregistreerd. Deze status wordt al vóór de provideroproep vastgelegd en bewijst op zichzelf niet dat die oproep daadwerkelijk begon. |
| `mail.mislukt` | De voorbereiding is definitief mislukt, zonder directe providerpoging. Een wachtrijstatus of tijdelijke fout met herstel geeft deze melding niet. |
| `mail.afgeleverd` | Een ondertekend Resend `email.delivered`-bewijs is gekoppeld aan het providerbericht van deze eigen mailpoging. Het bewijst geen lezen of klikken. |

Een handmatige bevestiging geeft geen `mail.geaccepteerd` of `mail.afgeleverd`. Gmail en Microsoft 365 leveren in deze keten geen bewijs voor `mail.afgeleverd`. Open- en klikmeldingen worden niet als API-event uitgegeven.

Mail gebruikt de volgende eigen envelop, zonder `job_id` of `run_id`:

```json
{
  "id": "00000000-0000-4000-8000-000000000005",
  "type": "mail.geaccepteerd",
  "created_at": "2026-10-04T12:05:00Z",
  "data": {
    "actie": "mail.verzenden",
    "object_soort": "mail",
    "object_id": "00000000-0000-4000-8000-000000000006",
    "outbox_id": "00000000-0000-4000-8000-000000000006",
    "poging_id": "00000000-0000-4000-8000-000000000007",
    "voorbereiding_poging": null
  }
}
```

`object_id` en `outbox_id` moeten gelijk zijn. Bij `mail.mislukt` is `poging_id` null en `voorbereiding_poging` de gehele voorbereidingsteller vanaf nul. Bij de andere drie mailtypes is `poging_id` een echte poging-UUID en `voorbereiding_poging` null. De melding bevat geen adres, onderwerp, berichtinhoud, bijlagen, reviewhash, provider-id of downloadlink. De Node.js-controlefunctie controleert deze getypeerde vormen na de handtekening. Dat voorbeeld is geschreven bron en nog niet uitgevoerd.

`mail.onzeker` wacht minimaal twee minuten. Vóór HTTP wordt gecontroleerd of dezelfde poging nog onzeker is. Een inmiddels achterhaalde melding stopt dan; een HTTP-verzoek dat al onderweg is kan niet worden teruggehaald. Ook een oude mislukte voorbereiding of vervangen poging wordt vóór verzending geweigerd. Ontvangstvolgorde is geen volgorde van de actuele toestand: lees na een melding de huidige mailstatus via `GET /v1/mail-outbox`.

In `GET /webhook-afleveringen` staan bij mail `run_ref` en `job_id` als null, plus `outbox_id`, `poging_id` en `voorbereiding_poging`. Dat historische leesmodel verschilt dus bewust van de verzonden envelop. Webhookafleverstatus `geaccepteerd` betekent alleen dat jouw server HTTP2xx gaf. Gebruik het eventtype om provideracceptatie en mailaflevering te onderscheiden.

Voor automatische en handmatige herhaling blijven de huidige sleutel-, actor-, organisatie-, abonnement-, mailgoedkeurings- en objectrechten gelden. De goedgekeurde afzender-, klant-, bron- en pakketbinding moet nog geldig zijn. Dit wordt ook gecontroleerd bij een herhaalde herstelontvangst. Journal en meldingen blijven maximaal dertig dagen beschikbaar; eerder vervallen onderliggende toegang kan toekomstige bezorging al stoppen. Een nieuw abonnement haalt geen oude mailmeldingen op.


## 4. Bekijk de aflevering en herstel een probleem

| Route | Betekenis |
|---|---|
| `GET /webhooks` | Maximaal 20 actieve abonnementen van deze sleutel, zonder secrets. |
| `GET /webhook-afleveringen?offset=0` | Maximaal 50 events met afleverstatussen; volg `volgende_offset` zolang die niet null is. |
| `GET /webhook-afleveringen/{id}` | Eén aflevering met maximaal 16 inhoudsvrije pogingregels. |
| `POST /webhook-afleveringen/{id}/herhaal` | Eén extra ronde na mislukking of stop. |
| `DELETE /webhooks/{id}` | Trek een abonnement in met body `{"bevestigd":true}`. |

Statussen: `wacht`, `bezig`, `geaccepteerd`, `mislukt`, `gestopt`. **Geaccepteerd** betekent dat jouw server 2xx teruggaf; het bewijst niet dat jouw verdere bedrijfsproces is geslaagd. Controleer dat in jouw eigen systeem.

Rapportly doet maximaal 8 automatische pogingen. Netwerkproblemen,429 en 5xx kunnen opnieuw worden geprobeerd met oplopende wachttijd, maximaal 15 minuten. Redirects en andere 4xx stoppen de automatische aflevering. Event-id blijft hetzelfde, ook als het antwoord van jouw server verloren ging. Reken daarom op mogelijke dubbele en niet-geordende meldingen.

Voor één bewuste extra ronde:

```http
POST /api/rapportly/v1/webhook-afleveringen/<aflevering_id>/herhaal
Authorization: Bearer <jouw_productiesleutel>
Idempotency-Key: crm-webhook-herstel-0001
Content-Type: application/json

{"bevestigd":true}
```

Dit geeft 202 en maximaal 8 aanvullende pogingen, maximaal 16 totaal. Herhaal dezelfde code bij een verbindingsprobleem. Een andere tweede herstelcode geeft 409. Het abonnement moet nog actief zijn en het event mag niet verlopen zijn. Bij iedere echte poging worden de sleutel, gebruiker, objectrechten en bewaartermijnen opnieuw gecontroleerd. Een ingetrokken sleutel of verloren toegang stopt toekomstige pogingen. Een HTTP-verzoek dat al onderweg is kan niet achteraf worden teruggetrokken.

## 5. Grenzen en eigen verantwoordelijkheid

Events en afleverhistorie blijven maximaal 30 dagen beschikbaar. Een onderliggende aanvraag kan eerder verlopen, bijvoorbeeld een briefing of stijlvoorstel; Rapportly stopt dan verdere aflevering. Nieuwe abonnementen krijgen alleen nieuwe events. Audio die inmiddels opnieuw is getranscribeerd maakt de oude audiomelding niet opnieuw bruikbaar.

De oefenomgeving simuleert geen echte netwerkaflevering. Excel-, workflow-, overige bron-/sjabloonacties zijn nog geen events in dit concept. Gerichte mailmeldingen zijn in dit document beschreven op bron3897cbd9. Beoordeel voor livegebruik de definitieve actielijst en releasehandleiding. Dit bestand beschrijft geschreven bron; de integrale tests en productiecontrole moeten nog plaatsvinden.

Een al afgerond rapport of gesprek blijft als gereed gemeld wanneer alleen een latere nevenactie mislukt. Haal de actuele resultaatstatus altijd opnieuw op; de melding is een aanleiding om je systeem bij te werken.
