Bouw een volledig geautomatiseerde transcriptie-pipeline met n8n

De meeste teams transcriberen nog steeds met de hand: iemand downloadt de opname, uploadt hem ergens, wacht, downloadt een SRT, hernoemt die en zet hem in de juiste map. Dat werkt prima — tot het veertig keer per week gebeurt.


Deze tutorial vervangt die persoon door een workflow. Zodra er een nieuwe opname binnenkomt, wordt hij getranscribeerd, ondertiteld en afgeleverd — zonder dat iemand meekijkt. We gebruiken n8n, omdat het overal draait, in je eigen infrastructuur gehost kan worden en met elke HTTP-API praat — ook met de onze.


Aan het eind heb je een workflow die:


  1. start zodra er een nieuw video- of audiobestand verschijnt
  2. het naar de Inwista-API stuurt om te transcriberen
  3. wacht tot de klus klaar is, zonder vooraf te gokken hoe lang dat duurt
  4. de afgeronde SRT downloadt
  5. hem aflevert waar je team hem nodig heeft


Alles hieronder draait op de openbare Inwista API v1. Geen plug-ins, geen eigen code-nodes.

Voordat je begint

Je hebt drie dingen nodig:


  • Een n8n-instantie — cloud of zelf gehost, versie 1.x
  • Een Inwista-API-sleutel — maak er een aan onder Mijn werkruimte → API-sleutels. Hij begint met inw_live_
  • Media die de API kan bereiken — de API verwacht een openbare https-URL, dus bestanden in Dropbox, Google Drive, S3 of je CMS hebben een deelbare of ondertekende link nodig


Een opmerking over de kosten voordat je iets bouwt dat zonder toezicht draait: transcriptie kost 4 credits per begonnen minuut media, in rekening gebracht zodra de klus wordt geaccepteerd. Mislukte klussen worden automatisch terugbetaald. Test de workflow met een paar korte bestanden voordat je hem op je archief loslaat.

Stap 1: kies je trigger

De pipeline begint bij de gebeurtenis die betekent: "er is iets nieuws om te transcriberen". De meest gebruikte opties:


  • Google Drive Trigger / Dropbox Trigger — een bewaakte map waar je team opnames in zet
  • Webhook — je eigen CMS of LMS roept n8n aan zodra een upload klaar is
  • Schedule Trigger — vraag een RSS-feed, een podcasthost of een database met onverwerkte rijen op
  • Manual Trigger — om te bouwen en te testen, en daar beginnen we mee

Voeg voorlopig een Manual Trigger toe. Vervang hem door de echte zodra de rest werkt.

Wat je ook kiest: de taak van de trigger is om precies één ding op te leveren — een openbaar bereikbare URL naar het mediabestand. Sla die op in een veld met de naam mediaUrl, zodat de rest van deze tutorial op je workflow aansluit.

Stap 2: dien de transcriptie in

Voeg een HTTP Request-node toe met de naam Submit transcription.

  • Methode — POST
  • URL — https://api.inwista.ai/v1/transcriptions
  • Authenticatie — Generic Credential Type → Header Auth
  • Headernaam — Authorization
  • Headerwaarde — Bearer inw_live_jouw_sleutel_hier
  • Send body — aan, JSON


Body:

{
  "source_url": "{{ $json.mediaUrl }}",
  "language": "nl",
  "diarization": true,
  "metadata": { "source": "n8n", "folder": "{{ $json.folderName }}" }
}


Drie velden die je moet begrijpen:

  • language is de taal die in het bestand gesproken wordt, niet de taal die je eruit wilt krijgen. Transcriberen in de brontaal levert nauwkeurige tijdcodes en schone tekst op; vertalen gebeurt daarna, als aparte stap, op het afgeronde transcript. Verwerkt je pipeline meerdere talen, koppel dit veld dan aan de map of de metadata van de trigger.


  • diarization zet sprekerlabels aan. Laat het uit bij content met één spreker — het kost verwerkingstijd die je niet nodig hebt.


  • metadata is van jou. Tot 1 KB aan wat je maar wilt, bij elke uitlezing ongewijzigd teruggegeven. Gebruik het voor de identificatoren waar je eigen systemen om draaien — een cursus-ID, een dossiernummer, de slug van een aflevering — zodat latere stappen de context niet hoeven te reconstrueren.


Voeg meteen nog een header toe:


  • Idempotency-Key{{ $execution.id }}


Als n8n de node opnieuw uitvoert na een netwerkstoring, herkent de API de sleutel en geeft hij de oorspronkelijke klus terug in plaats van een tweede te starten — en in rekening te brengen.

Het antwoord komt meteen binnen met status: "processing" en een id. De transcriptie is niet klaar; hij is geaccepteerd.

Stap 3: wacht netjes tot het klaar is

Hier gaat het bij de meeste pipelines mis. Een vaste "wacht vijf minuten" is te kort voor een college en verspilling bij een spraakmemo. Vraag in plaats daarvan de status op.

Voeg een Wait-node toe (Wait 15s), ingesteld op 15 seconden.

Voeg een HTTP Request-node toe (Check status):

  • Methode — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}
  • Authenticatie — dezelfde header als hiervoor

Voeg een IF-node toe (Is it done?) met deze voorwaarde:

{{ $json.status }}  equals  completed


Verbind de false-tak terug met Wait 15s. Dat is je lus: controleren, wachten, opnieuw controleren, tot de klus completed meldt. De true-tak gaat door naar de volgende stap.

Het antwoord bevat ook een progress-getal dat de echte stand van de pipeline weergeeft, dus als je een voortgangsindicator in Slack of je eigen dashboard wilt, staat die er al.


Twee dingen om toe te voegen voordat dit in de buurt van productie komt:

  • Vang fouten af. Voeg een tweede IF toe die {{ $json.status }} equals failed controleert en doorstuurt naar je alerting. Mislukte klussen worden automatisch terugbetaald, maar je wilt het wel weten.
  • Begrens de lus. De lusbeveiliging van n8n helpt, maar een expliciet plafond — een teller die na pakweg 80 rondes stopt — maakt van een vastgelopen klus een melding in plaats van een uitvoering die de hele nacht doorloopt.

Stap 4: haal de ondertitels op

Voeg een HTTP Request-node toe met de naam Get SRT:

  • Methode — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt
  • Response format — File (of Text, als je de inhoud in de flow wilt houden)

Het endpoint geeft het ondertitelbestand zelf terug, geen JSON-omhulsel — de uitvoer van de node is dus klaar om weg te schrijven, aan een e-mail te hangen of te uploaden.

Wissel format voor wat de bestemming nodig heeft:

Formaat — waarvoor

  • srt — videospelers, montageprogramma's, YouTube, de meeste CMS'en
  • vtt — HTML5-<track>, webspelers
  • txt — zoekindexen, LLM-pipelines, documentatie
  • json — tijdcodes op woordniveau, sprekerlabels, eigen weergave

Elk formaat is maar een weergave van dezelfde afgeronde klus. Er vier ophalen kost niets extra.

Stap 5: lever het af

De laatste node is wat "klaar" voor jouw team betekent:

  • Google Drive / Dropbox / S3 — zet het bestand naast de bronvideo
  • Slack — post het transcript in het kanaal dat erom vroeg
  • HTTP Request — duw het naar je CMS, je LMS of je ondertitelveld
  • Postgres / Airtable / Notion — bewaar de txt-versie als doorzoekbare tekst

Hoort je bestemming bij de diensten die Inwista al native integreert — Google Drive, OneDrive, SharePoint, Dropbox, Box, YouTube, Vimeo, Wistia en meer — sla deze node dan gerust helemaal over en stel cloudlevering in onder Mijn werkruimte → Integraties. De bestanden komen dan bij elke afgeronde klus automatisch binnen, met of zonder workflow.

Level up: vertalen vóór je aflevert

Zet twee nodes voor de aflevering en dezelfde pipeline levert ondertitels in zoveel talen als je wilt.

POST /v1/transcriptions/{id}/translate met:

{ "target_language": "no" }


Hij antwoordt met 202 Accepted — de vertaling draait op het afgeronde transcript, dus de timing klopt al en alleen de tekst verandert. Vraag GET /v1/transcriptions/{id}/translations/no op dezelfde manier op als de klus zelf en haal daarna op:

GET /v1/transcriptions/{id}/captions?format=srt&language=no


Loop dat door een lijst met doeltalen en één opname wordt in één uitvoering een complete meertalige ondertitelset. Let op: de taalaanvraag is strikt — vraag je een taal op zonder afgeronde vertaling, dan krijg je een expliciete fout in plaats van stilletjes de brontaal. Precies wat je wilt in een pipeline zonder toezicht.

Level up: ondertitels van broadcastkwaliteit

Een ruwe transcriptie is letterlijk. Ondertitels zijn vakwerk: regellengte, leessnelheid, de plek waar een zin over twee blokken breekt.

POST /v1/transcriptions/{id}/enhance haalt het afgeronde transcript door precies die behandeling — tekst indikken, regelafbrekingen opnieuw balanceren, dialoog opmaken, minimale en maximale blokduur afdwingen:

{
  "settings": {
    "maxLinesPerBlock": "2",
    "maxCharactersPerLine": 42,
    "textCondensation": "smart",
    "speakerDialogueFormat": "hyphens",
    "gapBetweenBlocks": "broadcasting"
  }
}


Hij antwoordt met 202; vraag GET /v1/transcriptions/{id}/enhancements/{enhancementId} op tot het klaar is. Daarna serveren de ondertitel-endpoints automatisch de verbeterde versie — je hoeft niets te wijzigen in je afleveringsnode.

Gevoelige opnames

Verwerkt je pipeline materiaal dat je liever niet bij ons laat staan — patiëntgesprekken, juridische opnames, interne personeelsbijeenkomsten — voeg dan één veld toe aan de aanvraag uit stap 2:

{
  "source_url": "{{ $json.mediaUrl }}",
  "language": "nl",
  "retention": "none"
}


Met retention: "none" wordt het bronbestand verwijderd zodra de transcriptie klaar is. Het transcript, de ondertitels, de vertalingen en latere verbeteringen blijven gewoon werken — alleen de audio en video zijn weg. Daarnaast is er store_media: false: het bestand blijft bewaard voor de verwerking, maar er worden geen afspeelkopieën gemaakt.

En als de bewaartermijn van een klus aan jouw kant afloopt, wist DELETE /v1/transcriptions/{id} alles wat erbij hoort — media, transcript, revisies, vertalingen — met één aanroep. Een geplande n8n-workflow die klussen ouder dan je bewaarbeleid verwijdert, is zo'n vier nodes, en maakt van je bewaarbeleid iets dat je kunt laten zien in plaats van beschrijven.

Webhooks: de tegenhanger

De status opvragen is de juiste besturing binnen één n8n-uitvoering: het staat op zichzelf, heeft geen openbare URL nodig en houdt de hele pipeline op één plek die je kunt debuggen.

Webhooks lossen een ander probleem op: afgerond werk bij één centrale ontvanger krijgen, waar de klus ook vandaan komt. Opnames die je collega's via het dashboard uploaden, klussen die een ander systeem indient, exports die uren later klaar zijn — het kan allemaal op één endpoint landen in plaats van dat elke workflow zijn eigen zaakjes regelt. Stel het in onder Mijn werkruimte → Integraties.

Richt hem op een n8n Webhook-node en je ontvangt een ondertekend transcript.completed-event:

{
  "event": "transcript.completed",
  "id": "evt_...",
  "timestamp": 1786902819,
  "workspaceId": "...",
  "project": {
    "id": "...",
    "name": "bestuursvergadering-augustus.mp4",
    "language": "nl",
    "durationSeconds": 3184
  },
  "files": [
    { "format": "srt", "name": "bestuursvergadering-augustus.srt", "url": "https://...", "expiresAt": 1786989219 }
  ]
}


Elke aanvraag is ondertekend met HMAC-SHA256 over de ruwe body, in de header X-Inwista-Signature als sha256=<hex>. Controleer die voordat je de payload vertrouwt — in n8n volstaan een Crypto-node en een IF-vergelijking. De bestands-URL's zijn ondertekend en 24 uur geldig: haal op wat je nodig hebt in plaats van de links te bewaren.

De aflevering wordt drie keer opnieuw geprobeerd, en een endpoint dat blijft falen wordt automatisch uitgeschakeld, met de reden zichtbaar in je integratie-instellingen — een kapotte ontvanger verschijnt dus als een status die je kunt zien, in plaats van events die stilletjes verdwijnen.

Waar je vroeg of laat tegenaan loopt

Rate limits. 300 leesaanvragen en 60 schrijfaanvragen per minuut per sleutel. Ruim voor normaal gebruik, snel bereikt als je een groot archief parallel loslaat zonder wachtrij. Werk je honderden bestanden bij, doe dat dan in batches.

Bron-URL's moeten bereikbaar zijn. De API test het bestand voordat hij de klus accepteert — zo staan duur en kosten vooraf vast. Een Drive-link die om inloggen vraagt levert unreadable_source op, en er wordt niets in rekening gebracht. Gebruik directe of ondertekende URL's.

Fouten zijn gestructureerd. Elke mislukking geeft dezelfde envelop terug:

{ "error": { "code": "insufficient_credits", "message": "..." } }


Vertak op code, nooit op de tekst van het bericht. Codes komen er binnen v1 alleen bij en worden nooit hernoemd; berichten kunnen wel worden herschreven.

Idempotency-keys gelden per aanvraag, niet per bestand. Ze beschermen je tegen dubbele facturering als dezelfde node-uitvoering opnieuw wordt geprobeerd. Dezelfde opname ontdubbelen wanneer twee verschillende runs hem indienen, blijft de taak van je workflow — een opzoektabel op bestands-ID is het gebruikelijke antwoord.

De complete workflow

Dit is het geraamte om te importeren en aan te passen. Vervang de credential-verwijzing door je eigen Header Auth-credential in plaats van een sleutel in de node te plakken.

{
  "name": "Inwista — automated transcription",
  "nodes": [
    {
      "parameters": {},
      "id": "trigger",
      "name": "When clicking Test workflow",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [0, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.inwista.ai/v1/transcriptions",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"source_url\": \"{{ $json.mediaUrl }}\", \"language\": \"nl\", \"diarization\": true}",
        "options": {}
      },
      "id": "submit",
      "name": "Submit transcription",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [220, 0]
    },
    {
      "parameters": { "amount": 15 },
      "id": "wait",
      "name": "Wait 15s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [440, 0]
    },
    {
      "parameters": {
        "url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}",
        "options": {}
      },
      "id": "status",
      "name": "Check status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [660, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "version": 2 },
          "conditions": [
            {
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "isdone",
      "name": "Is it done?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [880, 0]
    },
    {
      "parameters": {
        "url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt",
        "options": { "response": { "response": { "responseFormat": "text" } } }
      },
      "id": "srt",
      "name": "Get SRT",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1100, -100]
    }
  ],
  "connections": {
    "When clicking Test workflow": { "main": [[{ "node": "Submit transcription", "type": "main", "index": 0 }]] },
    "Submit transcription": { "main": [[{ "node": "Wait 15s", "type": "main", "index": 0 }]] },
    "Wait 15s": { "main": [[{ "node": "Check status", "type": "main", "index": 0 }]] },
    "Check status": { "main": [[{ "node": "Is it done?", "type": "main", "index": 0 }]] },
    "Is it done?": {
      "main": [
        [{ "node": "Get SRT", "type": "main", "index": 0 }],
        [{ "node": "Wait 15s", "type": "main", "index": 0 }]
      ]
    }
  }
}


Zes nodes. Al het andere — vertaling, verbetering, aflevering, verwijdering — hangt aan datzelfde geraamte.

Wat dit echt verandert

Het interessante is niet dat transcriptie geautomatiseerd wordt. Het is wat ophoudt een beslissing te zijn.


Als ondertitels een middag van iemand kosten, worden ze gerantsoeneerd: de belangrijke video's krijgen ze, de rest niet. Als ze per bestand niets kosten en er al zijn voordat iemand eraan denkt erom te vragen, houden ze op een project te zijn en worden ze een eigenschap van je content — elke opname doorzoekbaar, elke video toegankelijk, elke cursus conform, zonder dat er een vergadering aan te pas komt.


Dat is meer waard dan de uren die het bespaart.


Klaar om te bouwen? Maak een API-sleutel aan — het gratis abonnement is genoeg om dit van begin tot eind te doorlopen. De volledige endpointreferentie staat in de API-documentatie.