Créez un pipeline de transcription entièrement automatisé avec n8n

La plupart des équipes transcrivent encore à la main : quelqu'un télécharge l'enregistrement, l'envoie quelque part, attend, récupère un SRT, le renomme et le dépose dans le bon dossier. Cela fonctionne jusqu'au jour où l'opération se répète quarante fois par semaine.


Ce tutoriel remplace cette personne par un flux de travail. Dès qu'un nouvel enregistrement arrive, il est transcrit, sous-titré et livré — sans que personne n'ait à intervenir. Nous utiliserons n8n parce qu'il tourne n'importe où, peut être auto-hébergé au sein de votre propre infrastructure, et dialogue avec n'importe quelle API HTTP — y compris la nôtre.


À la fin, vous disposerez d'un flux de travail qui :


  1. se déclenche dès qu'un nouveau fichier vidéo ou audio apparaît
  2. l'envoie à l'API Inwista pour transcription
  3. attend la fin du traitement, sans pari sur sa durée
  4. télécharge le SRT terminé
  5. le livre là où votre équipe en a besoin


Tout ce qui suit repose sur l'API publique Inwista API v1. Aucun plugin, aucun nœud de code personnalisé.

Avant de commencer

Il vous faut trois choses :


  • Une instance n8n — cloud ou auto-hébergée, version 1.x
  • Une clé d'API Inwista — créez-en une dans Mon espace de travail → Clés d'API. Elle commence par inw_live_
  • Des médias accessibles à l'API — l'API attend une URL https publique : vos fichiers sur Dropbox, Google Drive, S3 ou dans votre CMS doivent donc disposer d'un lien de partage ou d'un lien signé


Un mot sur les coûts avant de construire quelque chose qui tournera sans surveillance : la transcription est facturée 4 crédits par minute entamée de média, débités à l'acceptation du travail. Les traitements en échec sont remboursés automatiquement. Testez le flux sur deux ou trois fichiers courts avant de le lancer sur vos archives.

Étape 1 : choisir votre déclencheur

Le pipeline démarre sur l'événement qui signifie « il y a quelque chose de nouveau à transcrire ». Les choix les plus courants :


  • Google Drive Trigger / Dropbox Trigger — un dossier surveillé dans lequel votre équipe dépose les enregistrements
  • Webhook — votre propre CMS ou LMS appelle n8n dès qu'un envoi se termine
  • Schedule Trigger — interrogez un flux RSS, un hébergeur de podcasts ou une base de données de lignes non traitées
  • Manual Trigger — pour construire et tester, et c'est par là que nous commençons

Ajoutez un Manual Trigger pour l'instant. Vous le remplacerez par le vrai déclencheur une fois le reste en place.

Quel que soit votre choix, le rôle du déclencheur est de produire une seule chose : une URL publiquement accessible vers le fichier média. Stockez-la dans un champ nommé mediaUrl pour que la suite du tutoriel corresponde à votre flux.

Étape 2 : soumettre la transcription

Ajoutez un nœud HTTP Request nommé Submit transcription.

  • Méthode — POST
  • URL — https://api.inwista.ai/v1/transcriptions
  • Authentification — Generic Credential Type → Header Auth
  • Nom de l'en-tête — Authorization
  • Valeur de l'en-tête — Bearer inw_live_votre_cle_ici
  • Send body — activé, JSON


Corps de la requête :

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


Trois champs à bien comprendre :

  • language est la langue parlée dans le fichier, et non celle que vous souhaitez obtenir. Transcrire dans la langue source, c'est ce qui produit des horodatages précis et un texte propre ; la traduction intervient ensuite, dans une étape distincte, sur la transcription terminée. Si votre pipeline traite plusieurs langues, faites correspondre le dossier ou les métadonnées du déclencheur à ce champ.


  • diarization active l'identification des locuteurs. Laissez-la désactivée pour un contenu à un seul intervenant — elle ajoute un temps de traitement dont vous n'avez pas besoin.


  • metadata vous appartient. Jusqu'à 1 Ko de contenu libre, renvoyé tel quel à chaque lecture. Utilisez-le pour transporter les identifiants qui comptent dans vos propres systèmes — un identifiant de cours, un numéro de dossier, le slug d'un épisode — afin que les étapes suivantes n'aient pas à reconstituer le contexte.


Ajoutez un en-tête supplémentaire pendant que vous y êtes :


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


Si n8n réessaie le nœud après un incident réseau, l'API reconnaît la clé et renvoie le travail d'origine au lieu d'en démarrer — et d'en facturer — un second.

La réponse arrive immédiatement avec status: "processing" et un id. La transcription n'est pas terminée : elle a été acceptée.

Étape 3 : attendre la fin, correctement

C'est ici que la plupart des pipelines dérapent. Un « attendre cinq minutes » fixe sera soit trop court pour un cours magistral, soit du gaspillage pour un mémo vocal. Interrogez l'API à la place.

Ajoutez un nœud Wait (Wait 15s) réglé sur 15 secondes.

Ajoutez un nœud HTTP Request (Check status) :

  • Méthode — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}
  • Authentification — la même que précédemment

Ajoutez un nœud IF (Is it done?) avec la condition :

{{ $json.status }}  equals  completed


Reliez la branche false à Wait 15s. Voilà votre boucle : vérifier, attendre, vérifier à nouveau, jusqu'à ce que le travail passe à completed. Reliez la branche true à l'étape suivante.

La réponse contient également un nombre progress qui reflète la position réelle du pipeline : si vous voulez un indicateur d'avancement dans Slack ou sur votre propre tableau de bord, il est déjà là.


Deux choses à ajouter avant de passer en production :

  • Gérez les échecs. Ajoutez un second IF qui teste {{ $json.status }} equals failed et redirige vers votre système d'alerte. Les travaux en échec sont remboursés automatiquement, mais vous voulez tout de même être prévenu.
  • Bornez la boucle. La protection intégrée de n8n aide, mais un plafond explicite — un compteur qui abandonne après, disons, 80 itérations — transforme un travail bloqué en alerte plutôt qu'en exécution qui tourne toute la nuit.

Étape 4 : récupérer les sous-titres

Ajoutez un nœud HTTP Request nommé Get SRT :

  • Méthode — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt
  • Response format — File (ou Text, si vous voulez le contenu dans le flux)

Le point de terminaison renvoie le fichier de sous-titres lui-même, et non un enrobage JSON — la sortie du nœud est donc prête à être écrite sur disque, jointe à un e-mail ou téléversée.

Changez format selon ce qu'attend la destination :

Format — à utiliser pour

  • srt — lecteurs vidéo, logiciels de montage, YouTube, la plupart des CMS
  • vtt — balise <track> HTML5, lecteurs web
  • txt — index de recherche, pipelines LLM, documentation
  • json — horodatage au mot, identification des locuteurs, rendu personnalisé

Chaque format n'est qu'une vue sur le même travail terminé. En récupérer quatre ne coûte rien de plus.

Étape 5 : livrer le résultat

Le dernier nœud, c'est ce que « terminé » veut dire pour votre équipe :

  • Google Drive / Dropbox / S3 — écrivez le fichier à côté de la vidéo source
  • Slack — publiez la transcription dans le canal qui l'a demandée
  • HTTP Request — poussez-la dans votre CMS, votre LMS ou votre champ de sous-titres
  • Postgres / Airtable / Notion — stockez la version txt comme texte interrogeable

Si votre destination fait partie de celles qu'Inwista intègre déjà nativement — Google Drive, OneDrive, SharePoint, Dropbox, Box, YouTube, Vimeo, Wistia et d'autres — envisagez de supprimer complètement ce nœud et de configurer la livraison cloud dans Mon espace de travail → Intégrations. Les fichiers arrivent alors automatiquement à chaque travail terminé, avec ou sans flux.

Aller plus loin : traduire avant de livrer

Insérez deux nœuds avant la livraison et le même pipeline produit des sous-titres dans autant de langues que nécessaire.

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

{ "target_language": "no" }


Il renvoie 202 Accepted — la traduction s'applique à la transcription terminée : les temps sont donc déjà justes et seul le texte change. Interrogez GET /v1/transcriptions/{id}/translations/no comme vous l'avez fait pour le travail, puis récupérez :

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


Bouclez sur une liste de langues cibles et un seul enregistrement devient un jeu complet de sous-titres multilingues en une seule exécution. Notez que la demande de langue est stricte : réclamer une langue sans traduction terminée renvoie une erreur explicite plutôt que de vous remettre discrètement la langue source — exactement ce que vous voulez dans un pipeline sans surveillance.

Aller plus loin : des sous-titres de qualité diffusion

Une transcription brute est verbatim. Les sous-titres, eux, relèvent d'un métier : longueur des lignes, vitesse de lecture, endroit où une phrase se coupe entre deux cartons.

POST /v1/transcriptions/{id}/enhance fait passer la transcription terminée par ce traitement — condensation du texte, rééquilibrage des retours à la ligne, mise en forme des dialogues, application des durées minimale et maximale des blocs :

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


Il renvoie 202 ; interrogez GET /v1/transcriptions/{id}/enhancements/{enhancementId} jusqu'à la fin du traitement. Ensuite, les points de terminaison de sous-titres servent automatiquement la version améliorée — aucun changement à faire dans votre nœud de livraison.

Traiter des enregistrements sensibles

Si votre pipeline traite des contenus que vous préférez ne pas nous voir conserver — entretiens médicaux, enregistrements juridiques, réunions internes — ajoutez un champ à l'envoi de l'étape 2 :

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


Avec retention: "none", le média source est supprimé dès la fin de la transcription. La transcription, les sous-titres, les traductions et toute amélioration ultérieure continuent de fonctionner normalement — seuls l'audio et la vidéo disparaissent. Il existe aussi store_media: false, qui conserve le fichier pour le traitement mais ne construit aucune copie de lecture.

Et lorsque la durée de conservation d'un travail arrive à son terme de votre côté, DELETE /v1/transcriptions/{id} efface tout ce qui s'y rapporte — média, transcription, révisions, traductions — en un seul appel. Un flux n8n planifié qui supprime les travaux plus anciens que votre politique représente environ quatre nœuds : votre politique de conservation devient alors quelque chose que vous pouvez démontrer plutôt que décrire.

Les webhooks : l'approche complémentaire

L'interrogation périodique est le bon mode de contrôle à l'intérieur d'une exécution n8n : elle est autonome, ne demande aucune URL publique et garde tout le pipeline en un seul endroit que vous pouvez déboguer.

Les webhooks résolvent un autre problème : acheminer le travail terminé vers un récepteur central, quelle que soit son origine. Les enregistrements que vos collègues envoient depuis le tableau de bord, les travaux soumis par un autre système, les exports qui se terminent des heures plus tard — tout peut atterrir sur un seul point de réception au lieu que chaque flux gère le sien. Configurez-le dans Mon espace de travail → Intégrations.

Pointez-le vers un nœud Webhook n8n et vous recevrez un événement transcript.completed signé :

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


Chaque requête est signée en HMAC-SHA256 sur le corps brut, dans l'en-tête X-Inwista-Signature sous la forme sha256=<hex>. Vérifiez-la avant de faire confiance à la charge utile — dans n8n, un nœud Crypto et une comparaison IF suffisent. Les URL de fichiers sont signées et valables 24 heures : récupérez ce dont vous avez besoin plutôt que de stocker les liens.

La livraison est réessayée trois fois, et un point de réception qui échoue durablement est désactivé automatiquement, avec la raison visible dans vos paramètres d'intégration — un récepteur cassé apparaît donc comme un statut que vous pouvez voir, plutôt que sous forme d'événements qui disparaissent en silence.

Ce qui finira par vous poser problème

Les limites de débit. 300 lectures et 60 écritures par minute et par clé. Généreux pour un usage normal, vite atteint si vous lancez une archive entière en parallèle sans file d'attente. Si vous rattrapez des centaines de fichiers, traitez-les par lots.

Les URL sources doivent être accessibles. L'API sonde le média avant d'accepter le travail — c'est ainsi que la durée et le coût sont connus d'avance. Un lien Drive qui exige une connexion renvoie unreadable_source, et rien n'est facturé. Utilisez des URL directes ou signées.

Les erreurs sont structurées. Chaque échec renvoie la même enveloppe :

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


Branchez sur code, jamais sur le texte du message. Les codes sont ajoutés au fil de l'eau dans v1 et ne sont jamais renommés ; les messages, eux, peuvent être reformulés.

Les clés d'idempotence valent par requête, pas par fichier. Elles vous protègent d'une double facturation lors des reprises d'une même exécution de nœud. Dédupliquer un même enregistrement soumis deux fois par deux exécutions différentes reste le travail de votre flux — une table de correspondance indexée sur l'identifiant du fichier est la réponse habituelle.

Le flux terminé

Voici le squelette à importer puis à adapter. Remplacez la référence d'identifiants par votre propre credential Header Auth plutôt que de coller une clé dans le nœud.

{
  "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\": \"fr\", \"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 }]
      ]
    }
  }
}


Six nœuds. Tout le reste — traduction, amélioration, livraison, suppression — se greffe sur ce même squelette.

Ce que cela change vraiment

L'intérêt n'est pas que la transcription soit automatisée. C'est ce qui cesse d'être une décision.


Quand des sous-titres coûtent l'après-midi de quelqu'un, on les rationne : les vidéos importantes y ont droit, les autres non. Quand ils ne coûtent rien par fichier et arrivent avant que quiconque pense à les demander, ils cessent d'être un projet pour devenir une propriété de vos contenus — chaque enregistrement consultable, chaque vidéo accessible, chaque formation conforme, sans qu'il faille en faire une réunion.


Cela vaut bien plus que les heures ainsi économisées.


Envie de le construire ? Créez une clé d'API — l'offre gratuite suffit pour dérouler ce tutoriel de bout en bout. La référence complète des points de terminaison se trouve dans la documentation de l'API.