# Inbedden en terugsturen

Standaard stuur je de medewerker naar de `hosted_url`, op het domein van Agreli. Je kunt de onboarding ook in je eigen site tonen met `embed.js`, en de medewerker na afloop terugsturen naar je eigen app met `return_url`. Ook de controle door de manager kan in je eigen product, zie [De controle in je eigen product](#de-controle-in-je-eigen-product). Voor alles moet je domein op de lijst met toegestane domeinen staan.

## Toegestane domeinen

Een eigenaar of manager zet de domeinen in het developer dashboard, onder **Toegestane domeinen**. Je kunt ze niet met een API-sleutel wijzigen. Zo kan iemand met een gelekte sleutel medewerkers niet naar een eigen pagina in de huisstijl van de werkgever sturen.

- Eén domein per regel, met `https://`, zoals `https://app.jouwapp.nl`. Een pad laten we weg. Jokertekens kunnen niet: noem elk subdomein apart. Hooguit 20 domeinen.
- De sandbox volgt de lijst van de organisatie zelf.
- Bij een softwarepartner tellen twee lijsten: die van de werkgever en die van de partner. Een partner zet zijn eigen domeinen één keer en kan ze dan gebruiken voor elke werkgever die hem toelaat.
- `GET /v1/organization` geeft de lijst terug in `allowed_origins`.

## Terugsturen met return_url

Geef bij het aanmaken `options.return_url` mee. Na het versturen toont de statuspagina een knop terug naar dat adres.

```json
{ "options": { "return_url": "https://app.jouwapp.nl/onboarding/klaar?sollicitant=4711" } }
```

- Alleen `https`, op een toegestaan domein. Anders krijg je `422 return_url_not_allowed` (geen toegestaan domein) of `422 return_url_invalid` (geen https-adres).
- Zet geen persoonsgegevens in het adres; een eigen kenmerk zoals een sollicitant-id kan wel.

## De onboarding in je eigen site

Laad `embed.js` en geef het de `hosted_url` uit `POST /v1/onboardings`. Het script maakt een iframe die meegroeit met de inhoud.

```html
<div id="onboarding"></div>
<script src="https://app.agreli.nl/embed.js"></script>
<script>
  const onboarding = Agreli.mount("#onboarding", {
    url: hostedUrl, // hosted_url uit POST /v1/onboardings
    onEvent: (e) => {
      if (e.type === "submitted") toonBedankt();
      if (e.type === "status") console.log("Stand:", e.state);
    },
  });
  // Later weghalen: onboarding.destroy();
</script>
```

De iframe werkt alleen op een toegestaan domein. Op elke andere site weigert de browser hem; daarvoor zet Agreli per link de kop `Content-Security-Policy: frame-ancestors` met jouw domeinen.

### Berichten aan je pagina

`onEvent` krijgt berichten met `source: "agreli"` en een `type`. Ze bevatten nooit persoonsgegevens.

| type | Wanneer | Extra |
| --- | --- | --- |
| `ready` | De pagina is geladen. | |
| `resize` | De hoogte van de inhoud verandert. `embed.js` past de iframe zelf aan. | `height` |
| `verified` | De medewerker heeft de eenmalige code bevestigd. | |
| `submitted` | De medewerker heeft de gegevens verstuurd. | |
| `signed` | De medewerker heeft het contract getekend. | |
| `status` | De stand van de onboarding, bij elke wijziging. | `state` |
| `return` | De medewerker klikt op de knop terug naar je `return_url`. De hele pagina gaat daarheen, niet alleen de iframe. | `return_url` |
| `closed` | De medewerker sluit af. | |

Vertrouw voor je administratie niet op deze berichten: ze komen uit de browser. Gebruik [webhooks](/docs/webhooks) of `GET /v1/onboardings/{id}` voor de echte stand.

### Goed om te weten

- **Inloggen.** Ook ingebed bevestigt de medewerker met een eenmalige code. Omdat browsers cookies van een ander domein in een iframe vaak blokkeren, onthoudt de pagina de sessie alleen zolang de iframe open is. Na herladen vraagt de medewerker een nieuwe code.
- **Camera.** `embed.js` geeft de iframe toestemming voor de camera, voor de scan van het identiteitsbewijs. Zet je zelf een `Permissions-Policy`, sta dan `camera` toe voor `https://app.agreli.nl`.
- **Je eigen CSP.** Heeft je site een Content-Security-Policy, voeg dan `https://app.agreli.nl` toe aan `script-src` (voor `embed.js`) en aan `frame-src`.
- **Zonder script.** Een eigen iframe kan ook: `https://app.agreli.nl/m/…?embed=1&origin=https://app.jouwapp.nl`, met `allow="camera"`. Berichten komen dan als `postMessage` binnen. Controleer altijd dat `event.origin` gelijk is aan `https://app.agreli.nl`.
- **Huisstijl.** De pagina toont het logo en de kleur van de werkgever, ook ingebed.

## De controle in je eigen product

Na het versturen controleert een manager de gegevens en keurt goed. Dat kan in je eigen product, zonder iemand naar Agreli te sturen. Het BSN en het identiteitsbewijs blijven bij Agreli: ze staan alleen in de iframe, nooit in je pagina, je API-antwoorden of je webhooks.

Goedkeuren blijft het werk van een mens met een eigen Agreli-account. Dezelfde regels gelden als in Agreli zelf: vierogen (één persoon vervult niet twee rollen), rechten per vestiging, en alleen de salarisadministratie ziet het volledige BSN.

### Zo werkt het

1. Je vraagt een controlelink aan voor één onboarding en één persoon, op het moment dat je de controle toont:

```bash
curl https://app.agreli.nl/api/public/v1/onboardings/onb_…/review-sessions   -H "Authorization: Bearer $AGRELI_API_KEY"   -H "Agreli-Organization: org_…"   -H "Content-Type: application/json"   -d '{ "reviewer": { "email": "sanne@restaurant.nl", "external_id": "user_4711", "name": "Sanne" } }'
```

```json
{
  "object": "review_session",
  "id": "rvs_…",
  "onboarding_id": "onb_…",
  "review_url": "https://app.agreli.nl/r/…",
  "expires_at": "2026-10-13T10:15:00.000Z",
  "reviewer": { "email": "sanne@restaurant.nl", "external_id": "user_4711", "name": "Sanne", "linked": false }
}
```

2. Je toont de `review_url` met `embed.js`, net als de onboarding:

```html
<div id="controle"></div>
<script src="https://app.agreli.nl/embed.js"></script>
<script>
  Agreli.mount("#controle", {
    url: reviewUrl, // review_url uit POST /v1/onboardings/{id}/review-sessions
    onEvent: (e) => {
      if (e.type === "approved") toonGoedgekeurd();
      if (e.type === "expired") vraagNieuweLinkAan();
    },
  });
</script>
```

3. De eerste keer logt de controleur in de iframe in met het eigen Agreli-account: een code per e-mail en de code uit de authenticator-app. Daarna is jouw gebruiker (`external_id`) 30 dagen gekoppeld aan dat account, en opent de controle direct. `reviewer.linked` zegt vooraf of inloggen nodig is.

### Regels

- **Recht.** Een softwarepartner heeft het recht `reviews:embed` nodig, en de werkgever moet dat toestaan. Een werkgever met een eigen sleutel kiest het recht bij het maken van de sleutel.
- **Een account bij de werkgever.** De controleur moet lid zijn van de werkgever in Agreli, met tweestapsverificatie aan. Anders lukt inloggen niet.
- **Eenmalig en kort geldig.** Een `review_url` opent één keer en is 15 minuten geldig. Daarna loopt de sessie in de iframe hooguit 2 uur. Vraag dus bij elke weergave een nieuwe link aan. Na herladen van de iframe vraag je ook een nieuwe aan.
- **Koppeling.** De koppeling hoort bij jouw `external_id` en het e-mailadres. Een ander id of een ander adres moet zelf inloggen. De controleur en de werkgever zien de koppelingen onder Instellingen, Beveiliging, en kunnen ze daar intrekken. De controleur krijgt bij het koppelen een e-mail.
- **Audit.** Een goedkeuring staat op naam van de controleur, met de vermelding dat het via jouw sleutel ging.

### Berichten aan je pagina

| type | Wanneer | Extra |
| --- | --- | --- |
| `ready` | De pagina is geladen. | |
| `resize` | De hoogte verandert. | `height` |
| `signed_in` | De controleur is ingelogd en gekoppeld. | |
| `verified` | De manager bevestigt dat het originele identiteitsbewijs is gezien. | |
| `approved` | De controleur heeft goedgekeurd. | `role` |
| `returned` | De controleur stuurt het dossier terug naar de medewerker. | |
| `status` | De stand van de onboarding. | `state` |
| `expired` | De link of sessie werkt niet meer. Vraag een nieuwe `review_url` aan. | |
| `closed` | De controleur logt uit. | |

Ook hier geldt: gebruik [webhooks](/docs/webhooks) of `GET /v1/onboardings/{id}` voor de echte stand.
