Llamadas transcritas hoy
47
↑ 12% vs ayer
Tasa de éxito webhook
99.1%
2 reintentos en 30 días
Latencia media callback
3.2s
Deepgram → endpoint
Flujo de transcripción asíncrona
Llamada grabada
WebRTC · WAV/MP3
→
POST Deepgram API
?callback=URL · nova-2-es
→
Deepgram procesa
Retorna request_id
→
Webhook callback
POST /webhooks/deepgram
→
Guarda en Supabase
tabla transcriptions
→
Notifica abogado
SendGrid email
📄
webhook.handler.js — VoxLegal
import { createClient } from '@supabase/supabase-js'; import sgMail from '@sendgrid/mail'; const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY ); // ── Middleware: verificar dg-token ────────────────── const verifyDgToken = (req, res, next) => { const dgToken = req.headers['dg-token']; if (!dgToken) { return res.status(401).json({ error: 'Missing dg-token' }); } if (dgToken !== process.env.DEEPGRAM_API_KEY_ID) { return res.status(403).json({ error: 'Invalid dg-token' }); } next(); }; // ── Endpoint principal ────────────────────────────── router.post( '/webhooks/deepgram', express.raw({ type: 'application/json' }), verifyDgToken, async (req, res) => { try { const result = JSON.parse(req.body.toString()); const { request_id, results, duration } = result; const transcript = results.channels[0].alternatives[0].transcript; const confidence = results.channels[0].alternatives[0].confidence; // 1. Guardar en Supabase const { data, error } = await supabase .from('transcriptions') .update({ status: 'completed', transcript, confidence, duration_s: duration, completed_at: new Date().toISOString(), }) .eq('deepgram_request_id', request_id) .select('lawyer_email, case_ref'); if (error) throw error; // 2. Notificar al abogado (SendGrid) if (data?.[0]?.lawyer_email) { await sgMail.send({ to: data[0].lawyer_email, from: 'actas@voxlegal.io', subject: `Acta lista · Expediente ${data[0].case_ref}`, text: `Transcripción completada. Confianza: ${confidence}`, }); } // Deepgram NO reintentará si devolvemos 2xx return res.status(200).json({ ok: true }); } catch (err) { console.error('[deepgram-webhook] Error:', err); // Retornar 5xx → Deepgram reintentará hasta 10x return res.status(500).json({ error: 'Processing failed' }); } } );
📦
Payload Deepgram (ejemplo real)
{ "request_id": "a3e8f2c1-9b4d-4f2a-8e1c-7d3b2a9f0e55", "created": "2026-06-16T11:23:44.182Z", "duration": 487.34, "channels": 2, "results": { "channels": [{ "alternatives": [{ "transcript": "Buenos días, señor Martínez. Tal como acordamos, la cláusula de no competencia quedará limitada a un radio de cincuenta kilómetros durante un período máximo de dos años tras la extinción del contrato.", "confidence": 0.9873, "words": "[... 894 words ...]" }] }] } }
📋
Logs del servidor (Railway)
11:23:44.201
INFO
POST /webhooks/deepgram recibido · IP: 52.41.42.151
11:23:44.203
AUTH
dg-token válido · API Key ID: dg_ak_7x9Kp3mNq2
11:23:44.205
INFO
request_id: a3e8f2c1 · duración: 487.34s · confianza: 98.73%
11:23:44.231
DB
Supabase UPDATE OK · expediente EXP-2026-0847
11:23:44.419
EMAIL
SendGrid → lcalderon@voxlegal.io · Acta lista · EXP-2026-0847
11:23:44.421
OK
HTTP 200 · Deepgram NO reintentará · latencia: 220ms
🔐
Variables de entorno (.env)
Las variables marcadas con (*) son SECRETAS — nunca al repositorio.
DEEPGRAM_API_KEY=
dg_prod_xxxxxxxxxxxxxxxxxxxx
(*) Para solicitar transcripciones
DEEPGRAM_API_KEY_ID=
dg_ak_7x9Kp3mNq2
Del panel Deepgram · valida dg-token
WEBHOOK_URL=
https://voxlegal.railway.app/webhooks/deepgram
Puerto 443 (HTTPS)
SUPABASE_URL=
https://xyzcompany.supabase.co
(*) Base de datos
SUPABASE_SERVICE_KEY=
eyJhbGciOiJIUzI1NiIsInR5cCI...
(*) Service role key
SENDGRID_API_KEY=
SG.xxxxxxxxxxxxxxxxxxxxx
(*) Notificaciones email
📊
Restricciones y comportamiento Deepgram
Deepgram solo permite callbacks en puertos 80, 443, 8080 y 8443.
| Comportamiento | Detalle | VoxLegal |
|---|---|---|
| Puertos permitidos | 80, 443, 8080, 8443 | 443 OK |
| Reintentos ante fallo | Hasta 10 veces | Manejado |
| Delay entre reintentos | 30 segundos | Idempotente |
| Autenticación | dg-token header | Verificado |
| HMAC signature | No disponible | Token-based |
| Respuesta esperada | HTTP 200-299 | 200 JSON |
Petición de transcripción con callback
💻
curl — Enviar audio a Deepgram con callback
# VoxLegal: transcribir llamada EXP-2026-0847 con callback a Railway curl \ --request POST \ --header 'Authorization: Token $DEEPGRAM_API_KEY' \ --header 'Content-Type: audio/wav' \ --data-binary @llamada-exp-2026-0847.wav \ --url 'https://api.deepgram.com/v1/listen? model=nova-2& language=es& diarize=true& punctuate=true& callback=https://voxlegal.railway.app/webhooks/deepgram' # Respuesta inmediata (Deepgram procesa en background): # { "request_id": "a3e8f2c1-9b4d-4f2a-8e1c-7d3b2a9f0e55" } # → Deepgram enviará el resultado al webhook cuando termine # Desarrollo local con Hookdeck CLI: npx hookdeck-cli listen 3000 deepgram --path /webhooks/deepgram # Genera: https://events.hookdeck.com/e/src_XXXX → localhost:3000