# doGet en doPost voor webhooks ## Web app-deployment Apps Script kun je als web app deployen, zodat het HTTP-verzoeken ontvangt. Daarmee wordt je script een webhook-ontvanger, API-endpoint of server-side verwerker. Twee gereserveerde functies doen het werk: `doGet(e)` vangt GET-verzoeken op en `doPost(e)` vangt POST-verzoeken op. Beide krijgen een event-object `e` mee met de request-gegevens. :::howto title="Zo deploy je een script als web app" 1. Open je script en kies **Deploy > New deployment**. 2. Kies bij **Type** de optie **Web app**. 3. Stel **Uitvoeren als** in: jij (vaste rechten) of de aanvragende gebruiker. 4. Stel **Wie heeft toegang** in. Voor publieke webhooks kies je **Anyone**. 5. Klik op **Deploy** en noteer de deployment-URL. ::: Let op: elke keer dat je code wijzigt, moet je een **nieuwe versie** van de deployment publiceren via **Manage deployments**, anders draait de oude code op de bestaande URL. ## doGet implementeren `doGet` is handig voor statuschecks, het uitleveren van publieke data of het tonen van een HTML-pagina. Lees de querystring uit via `e.parameter`. ```javascript function doGet(e) { const actie = e.parameter.actie || 'status'; switch (actie) { case 'status': return antwoordJson({ status: 'ok', tijdstip: new Date().toISOString() }); case 'data': const data = haalPubliekeDataOp(); return antwoordJson({ succes: true, data }); case 'formulier': return toonFormulier(); default: return antwoordJson({ fout: 'Onbekende actie' }); } } function antwoordJson(data) { return ContentService .createTextOutput(JSON.stringify(data)) .setMimeType(ContentService.MimeType.JSON); } function toonFormulier() { return HtmlService.createHtmlOutputFromFile('formulier') .setTitle('Aanvraagformulier'); } ``` :::warn title="Web apps sturen geen HTTP-statuscodes" ContentService en HtmlService geven altijd HTTP 200 terug. Je kunt dus geen 400 of 401 als statuscode meesturen. Communiceer fouten in plaats daarvan via een veld in de JSON-body, bijvoorbeeld `{ "fout": "Onbekende actie" }`, en laat de client daarop reageren. ::: ## doPost implementeren `doPost` ontvangt de request-body via `e.postData`. Bij JSON gebruik je `e.postData.contents`, bij een gewone form-post lees je `e.parameter` uit. ```javascript function doPost(e) { try { const contentType = e.postData ? e.postData.type : ''; let body; if (contentType === 'application/json') { body = JSON.parse(e.postData.contents); } else { body = e.parameter; } valideerWebhookBody(body); const resultaat = verwerkWebhookData(body); return antwoordJson({ succes: true, ...resultaat }); } catch (err) { console.error('doPost fout: ' + err.message); return antwoordJson({ succes: false, fout: err.message }); } } function valideerWebhookBody(body) { if (!body || typeof body !== 'object') { throw new Error('Ongeldige request body'); } } function verwerkWebhookData(body) { console.log('Webhook ontvangen: ' + JSON.stringify(body)); return { verwerkt: true }; } ``` ## Webhook-authenticatie Een web app met toegang voor **Anyone** vraagt geen login. Iedereen die de URL kent, kan een verzoek sturen. Beveilig je endpoint daarom met een geheim token dat je in `PropertiesService` bewaart en bij elk verzoek controleert. ```javascript function doPost(e) { const geheimToken = PropertiesService.getScriptProperties().getProperty('WEBHOOK_SECRET'); const ontvangenToken = e.parameter.token || ''; if (ontvangenToken !== geheimToken) { return antwoordJson({ fout: 'Ongeldig token' }); } return verwerkGeverifieerdVerzoek(e); } ``` :::tip title="Vergelijk tokens veilig en bewaar geheimen apart" Zet het secret nooit hardcoded in je code, maar in **Project Settings > Script Properties**. Wil je timing-aanvallen tegengaan, gebruik dan een lengte- plus karaktervergelijking in plaats van een directe string-match, of werk met een HMAC-handtekening zoals hieronder bij GitHub. ::: ## Slack webhook-ontvanger Slack stuurt eerst een `url_verification`-verzoek waarop je de `challenge` moet terugkaatsen. Daarna komen events binnen als `event_callback`. ```javascript function doPost(e) { try { const body = JSON.parse(e.postData.contents || '{}'); if (body.type === 'url_verification') { return antwoordJson({ challenge: body.challenge }); } if (body.type === 'event_callback') { verwerkSlackEvent(body.event); } return antwoordJson({ ok: true }); } catch (err) { console.error(err.message); return antwoordJson({ ok: false }); } } function verwerkSlackEvent(event) { if (event.type === 'message' && !event.bot_id) { const blad = SpreadsheetApp.openById('SHEET_ID').getActiveSheet(); blad.appendRow([new Date(), event.user, event.text, event.channel]); } } ``` ## Formulier-POST verwerken Een HTML-formulier dat naar je web app post, lever je af in `e.parameter`. Valideer de velden en geef een nette HTML-bevestiging terug. ```javascript function doPost(e) { const naam = e.parameter.naam || ''; const email = e.parameter.email || ''; const bericht = e.parameter.bericht || ''; if (!naam || !email || !bericht) { return antwoordHtml('

Alle velden zijn verplicht.

'); } if (!email.includes('@')) { return antwoordHtml('

Ongeldig e-mailadres.

'); } GmailApp.sendEmail('info@bedrijf.nl', 'Contactformulier: ' + naam, 'Van: ' + naam + ' (' + email + ') ' + bericht); return antwoordHtml('

Bedankt! Je bericht is ontvangen.

'); } function antwoordHtml(inhoud) { return HtmlService.createHtmlOutput('' + inhoud + ''); } ``` ## GitHub-webhook met handtekeningcontrole GitHub ondertekent elk verzoek met een HMAC-SHA256-handtekening in de header. Apps Script geeft request-headers niet rechtstreeks door aan `doPost`, dus de gangbare aanpak is een gedeeld secret valideren op de payload zelf, of GitHub het secret laten meesturen waar je dat kunt. :::howto title="Een GitHub-webhook opzetten" 1. Deploy je script als web app met **Anyone** toegang. 2. Voeg de web app-URL toe als webhook in GitHub via **Settings > Webhooks**. 3. Stel een **secret** in en bewaar dezelfde waarde in `PropertiesService`. 4. Bereken in `doPost()` met `Utilities.computeHmacSha256Signature()` de handtekening over `e.postData.contents` en vergelijk die met de meegestuurde waarde. 5. Parse pas daarna de body met `JSON.parse(e.postData.contents)`. 6. Verwerk het event op basis van `body.action` of `body.ref`. ::: ## Gelijktijdige verzoeken en uitvoertijd Een web app mag per uitvoering maximaal 6 minuten draaien (de runtime-quota van Apps Script). Een GET- of POST-verzoek dat daar overheen gaat, wordt afgebroken. Houd er rekening mee dat de aanroepende kant (een browser of `UrlFetchApp`) vaak een veel kortere eigen time-out heeft, dus mik op verwerking binnen enkele seconden. Komen er meerdere webhooks tegelijk binnen die hetzelfde Sheet of dezelfde Property bijwerken, gebruik dan de **Lock Service** om race-condities te voorkomen. ```javascript function veiligToevoegen(rij) { const lock = LockService.getScriptLock(); lock.waitLock(10000); try { SpreadsheetApp.openById('SHEET_ID').getActiveSheet().appendRow(rij); } finally { lock.releaseLock(); } } ``` :::faq ### Hoe krijg ik de web app-URL na deployment? Je ziet de URL in het deployment-scherm direct na publicatie. In een gebonden script kun je de actieve URL ook ophalen met ScriptApp.getService().getUrl(). ### Heeft doGet toegang tot Session.getActiveUser()? Alleen als de web app is ingesteld op uitvoeren als de gebruiker die de app opent en die gebruiker is ingelogd met een Workspace-account. Bij toegang voor Anyone of bij anonieme bezoekers geeft getActiveUser() een lege string terug. ### Hoe debug ik een web app? Schrijf met console.log() naar de Cloud-logs, zichtbaar onder Executions in de editor. Test losse functies door ze handmatig aan te roepen met een nagebootst event-object, bijvoorbeeld doGet({ parameter: { actie: 'status' } }). ### Hoelang mag een web app-verzoek draaien? Apps Script staat maximaal 6 minuten per uitvoering toe. De client die het verzoek doet, heeft vaak een kortere eigen time-out, dus reken op snelle responses. Voor langlopend werk sla je de taak op en verwerk je die later via een time-driven trigger. ### Waarom krijg ik geen 401 of 400 terug bij een fout? ContentService en HtmlService antwoorden altijd met HTTP 200. Geef de foutstatus mee in de JSON-body, bijvoorbeeld een veld fout of een boolean succes, en laat de client daarop controleren. ### Hoe lees ik request-headers in doPost? Apps Script geeft custom HTTP-headers niet door aan doPost. Werk in plaats daarvan met een token in de querystring of valideer een HMAC-handtekening over de payload met Utilities.computeHmacSha256Signature(). ::: Met `doGet` en `doPost` wordt Apps Script een volwaardig HTTP-endpoint voor webhooks, API's en formulierverwerking. Combineer het met de Lock Service voor thread-veilige verwerking en met `PropertiesService` voor veilige opslag van geheimen.