Voice notes are the dominant format on WhatsApp (especially in LATAM) — and they're annoying to listen to in a meeting or in a rush. Cloud STT services receive your audio; WhatsApp's own transcription is mediocre. This pipeline transcribes every incoming voice note — and the ones you send yourself — with faster-whisper running locally on a CPU-only node, and the text comes back as a normal message. It started as a personal tool; today it is a shared transcription engine several engineers use daily, with other services hanging off the same endpoint. The audio never leaves the network.
A 3-second voice note took as long as a 30-second one. The engine pads every clip to a fixed 30-second window, so short notes - which is almost all of them - paid for silence. The fix was not a faster GPU: it was an int8 runtime that uses the AVX-512 VNNI instructions already sitting in the CPU, plus voice-activity detection so silence is never transcribed at all. Measured across three durations: 7.1x, 6.9x and 6.0x at the engine, 3.2x end to end once model load and format conversion are counted. Both numbers are here because they are not the same number.
Runs the WhatsApp daemon (whatsapp-web.js, multi-account), captures the audio, and writes it to audio-inbox/. WhatsApp needs a persistent session to receive messages, so it lives wherever that daemon runs and survives reboots.
A worker polls the queue every few seconds, pulls the file, ffmpeg ogg→wav 16 kHz, then faster-whisper large-v3-turbo in int8 on CPU. No accelerator is scheduled, woken or reserved: the node is always on and costs a few watts.
The pipeline never changed shape. What changed is the engine in the third box - and finding out that its cost had nothing to do with how long you spoke.
faster-whisper large-v3-turbo int8 on CPU — no audio ever leaves your network. Multi-account: captures all your WhatsApp accounts at once.
Send yourself a voice note (an idea on the fly) and get the transcription back in your self-chat. Ideal for memos while driving, or for feeding long audio to an LLM that doesn't accept voice.
The transcription includes account, time, sender name, and group. Transport over base64 over SSH — accents and line breaks survive correctly.
Files are deleted from the capture node only after successful delivery to the self-chat. Nothing gets lost or processed twice.
// WRONG — these will never match if (msg.from === msg.to) { ... } // WhatsApp serializes from as <number>@c.us // and to as <id>@lid — never equal // RIGHT const dest = await client.getContactById(msg.to); if (dest && dest.isMe) { enqueue(msg, "self"); }
Detecting a self-forward by comparing from === to doesn't work in WhatsApp Web: from is <number>@c.us and to is <id>@lid — two strings that are never equal even for the same person. The correct way is getContactById(to).isMe.
Depending on the library has a real cost: when WhatsApp renamed an internal field of the message id, audio downloads stopped for a full day — with text still flowing and every monitor green. The check that now covers it walks the same path production walks rather than an adjacent one, because the first version never serialised an id and would have stayed green throughout the outage.