# Connect Rapportly webhooks without support

This guide describes the available API within the documented release limits. Current key, user, package and object permissions remain required. Not every website feature is available through the API.

A webhook notifies your server when a request completes, definitively fails or an associated email changes state. The new API channel sends technical references. Retrieve the actual content separately through the API with suitable read permissions. The existing Zapier integration uses a separate message format.

## 1. Prepare your server

Use a production key with `webhooks:beheren` and the permissions for the processing you start. Subscription and processing must use the **same key**. A team administrator can prepare that key for a member and limit its permissions.

Create a public HTTPS endpoint on your server. IP address hosts, internal destinations, URL credentials and redirects are rejected. Keep all secrets in server secret storage. Read the **original request bytes**, capped at 32 KiB, before parsing JSON. Store each event id durably with a unique constraint.

Start with fictional input and a separate subscription. The API practice environment sends no real webhooks. This integration uses production; starting actual processing may consume credits.

## 2. Subscribe

Base URL: `https://api.rapportly.io/api/rapportly/v1`.

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

{
  "url": "https://your-own-domain.com/rapportly-events",
  "events": ["verwerking.gereed", "verwerking.mislukt"],
  "bevestigd": true
}
```

A 201 response contains `id`, `url`, `events`, `actief`, `aangemaakt_op`, `signing_secret` and `herhaald:false`. Store the signing secret safely with the subscription. It is not included in list responses. If the response is lost, repeat the identical body and Idempotency-Key:200 returns the same subscription and secret with `herhaald:true`. Different input with that key gives 409. A revoked subscription stays revoked:410.

There may be up to 20 active subscriptions per key. URLs and event selections are immutable. To change them, create a subscription with a new request key and revoke the old one. Never place the signing secret in logs, browser code, support tickets or examples.

## 3. Verify every delivery

The four headers are:

- `X-Rapportly-Event`: one of the six event types described here.
- `X-Rapportly-Event-Id`: a stable event UUID, including retries.
- `X-Rapportly-Timestamp`: Unix seconds, refreshed for each attempt.
- `X-Rapportly-Signature`: `v1=` followed by a hexadecimal HMAC-SHA256.

Sign `timestamp + '.' + event_id + '.' + exact raw body bytes` with this subscription's signing secret. Allow at most five minutes of clock difference and compare signatures with a constant-time comparison. Then require body event id/type to match the headers. Keep your server clock accurate.

The accompanying `rapportly-webhook-verify.mjs` exports a Node.js verification function. It is not a complete HTTP server. Invalid input returns `null`; valid input returns the parsed event. In **one database transaction**, insert the unique event id and enqueue your own processing task. If the event id already exists, do not enqueue another task. Only then return200, 202 or 204 promptly. Run slow processing in your own queue. This avoids acknowledging a request that a crash could otherwise lose.

```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"
  }
}
```

The envelope contains no customer names, transcripts, prompts, email addresses, generated content or download links. Nine job types are currently connected in source: report generation (also quotes/presentations), conversation generation, report revision, conversation revision, audio transcription, customer briefing, modern style test, classic style refinement and style workshop proposal. An intermediate error still eligible for recovery does not generate a definitive failure event.

## 3a. Recognise the four email notifications

Select one to six unique events: the two processing events above and `mail.geaccepteerd`, `mail.onzeker`, `mail.mislukt`, `mail.afgeleverd`. Select only events your receiver handles. Email notifications belong to the original outbox approved through the same API key. Existing native emails are not automatically adopted.

| Event | What it proves |
|---|---|
| `mail.geaccepteerd` | The sending provider accepted this attempt. This does not prove delivery to the recipient. |
| `mail.onzeker` | This attempt is still recorded as uncertain. This state is saved before the provider call and does not itself prove that the call actually started. |
| `mail.mislukt` | Preparation failed definitively, without a direct provider attempt. Queued work or a temporary error still eligible for recovery does not emit this event. |
| `mail.afgeleverd` | A signed Resend `email.delivered` receipt matches the provider message for this email attempt. It does not prove reading or clicking. |

Manual confirmation does not emit `mail.geaccepteerd` or `mail.afgeleverd`. Gmail and Microsoft 365 do not provide evidence for `mail.afgeleverd` through this flow. Opens and clicks are not emitted as API events.

Email uses its own envelope, without `job_id` or `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` and `outbox_id` must match. For `mail.mislukt`, `poging_id` is null and `voorbereiding_poging` is the nonnegative integer preparation counter. For the other three email types, `poging_id` is a real attempt UUID and `voorbereiding_poging` is null. Notifications contain no address, subject, message content, attachments, review hash, provider id or download link. The Node.js function checks these typed shapes after verifying the signature. This example is written source and has not been executed yet.

`mail.onzeker` waits at least two minutes. Before HTTP delivery, Rapportly checks whether the same attempt is still uncertain. An outdated notification is stopped; an HTTP request already in flight cannot be recalled. An outdated failed preparation or replaced attempt is also rejected before delivery. Arrival order does not establish current state: retrieve the current email status after a notification through `GET /v1/mail-outbox`.

In `GET /webhook-afleveringen`, email history includes null `run_ref` and `job_id`, plus `outbox_id`, `poging_id` and `voorbereiding_poging`. This historical read model deliberately differs from the transmitted envelope. Webhook delivery status `geaccepteerd` means only that your server returned HTTP2xx. Use the event type to distinguish provider acceptance from email delivery.

Automatic and manual retries remain subject to current key, actor, organisation, subscription, email approval and object permissions. Approved sender, customer, source and package bindings must remain valid. These checks also apply when replaying a recovery receipt. Events and delivery history remain available for up to thirty days; underlying access expiring sooner can stop delivery earlier. A new subscription does not retrieve older email notifications.


## 4. Inspect and recover delivery

| Endpoint | Purpose |
|---|---|
| `GET /webhooks` | Up to 20 active subscriptions for this key, without secrets. |
| `GET /webhook-afleveringen?offset=0` | Up to 50 events and their delivery statuses; follow `volgende_offset` until null. |
| `GET /webhook-afleveringen/{id}` | A delivery and up to 16 attempt records without content. |
| `POST /webhook-afleveringen/{id}/herhaal` | One explicit extra round after a failed or stopped delivery. |
| `DELETE /webhooks/{id}` | Revoke with body `{"bevestigd":true}`. |

Statuses are `wacht` (waiting), `bezig` (in progress), `geaccepteerd` (accepted), `mislukt` (failed), and `gestopt` (stopped). **Accepted** means your server returned 2xx; it does not prove your subsequent business process succeeded. Monitor that in your system.

Rapportly makes up to 8 automatic attempts. Network failures,429 and 5xx may be retried with increasing delays, capped at 15 minutes. Redirects and other 4xx responses stop automatic delivery. An event keeps the same id if your response is lost. Handle duplicate and out-of-order events.

Request one intentional extra round:

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

{"bevestigd":true}
```

This returns 202 and permits up to 8 further attempts, up to 16 total. Retry the same request key after a connection problem. A different second recovery key gives 409. The subscription must be active and the event unexpired. Every delivery rechecks current key, user, object permissions and source retention. Revocation or loss of access stops future attempts. A request already in flight cannot be recalled.

## 5. Limits

Events and delivery history are retained for up to 30 days. An underlying request may expire sooner, for example a briefing or style proposal; further delivery then stops. New subscriptions receive only new events. If audio has been transcribed again, an old audio event is no longer eligible for delivery.

The practice environment does not simulate real network delivery. Excel, workflow, other source/template actions are not events in this draft yet. Targeted email notifications are described here from source3897cbd9. Check the final action catalogue and release guide before production use. This guide describes source code; integrated tests and production verification remain outstanding.

An already completed report or conversation remains a completed result if only a later side effect fails. Always retrieve the current result status; the notification prompts your system to refresh its view.
