Pular para o conteúdo principal

Webhooks

Os webhooks permitem que sua aplicação receba notificações em tempo real quando jobs assíncronos mudam de status. Em vez de fazer polling na API de Jobs, você pode configurar um endpoint de webhook para receber automaticamente atualizações quando jobs são criados, iniciados, concluídos ou falham.

Visão Geral

Os webhooks da TranscriMed são requisições HTTP POST enviadas para seu endpoint configurado sempre que eventos específicos ocorrem. Todas as requisições de webhook são assinadas com HMAC-SHA256 para segurança e incluem informações detalhadas sobre o evento no payload.

Recursos Principais

  • Notificações em tempo real para eventos de jobs assíncronos
  • Requisições assinadas com HMAC-SHA256 para segurança
  • Tentativas automáticas com backoff exponencial
  • Conteúdo completo do registro médico em webhooks de jobs concluídos
  • Logs de entrega e monitoramento no Portal do Desenvolvedor
  • Funcionalidade de endpoint de teste

Eventos de Webhook

Configure quais eventos você deseja receber:

EventoDescrição
job.createdDisparado quando um job assíncrono é criado e enfileirado
job.startedDisparado quando um job começa a ser processado
job.completedDisparado quando um job termina com sucesso
job.failedDisparado quando um job falha com erros
job.cancelledDisparado quando um job é cancelado

Configuração

1. Configurar Endpoint de Webhook

No Portal do Desenvolvedor, configure seu webhook baseado no tipo de cliente:

Para Clientes de Teste (desenvolvimento/testes):

  1. URL do Webhook de Teste: URL do seu endpoint (HTTP localhost permitido, ex: http://localhost:3000/webhooks/transcrimed)
  2. Eventos: Selecione quais eventos receber
  3. Gerar Secret: Crie um secret de webhook de teste para verificação de assinatura
  4. Habilitar: Ative as notificações de webhook de teste

Para Clientes de Produção (integração ao vivo):

  1. URL do Webhook de Produção: Seu endpoint HTTPS (ex: https://sua-api.com/webhooks/transcrimed)
  2. Eventos: Selecione quais eventos receber
  3. Gerar Secret: Crie um secret de webhook de produção para verificação de assinatura
  4. Habilitar: Ative as notificações de webhook de produção

2. Implementar Endpoint de Webhook

Seu endpoint de webhook deve:

  • Aceitar requisições HTTP POST
  • Verificar assinaturas HMAC (recomendado)
  • Responder com status HTTP 200 para processamento bem-sucedido
  • Processar requisições de forma idempotente (o mesmo payload pode ser enviado múltiplas vezes)

Exemplo de Implementação (Node.js):

const express = require('express');
const crypto = require('crypto');
const app = express();

// Middleware para capturar body raw para verificação de assinatura
app.use('/webhooks/transcrimed', express.raw({ type: 'application/json' }));

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}

app.post('/webhooks/transcrimed', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookSecret = process.env.TRANSCRIMED_WEBHOOK_SECRET;

// Verificar assinatura
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).send('Assinatura inválida');
}

const payload = JSON.parse(req.body);

// Processar evento do webhook
switch (payload.event) {
case 'test.ping':
// Webhook de teste do Portal do Desenvolvedor - apenas responder com 200
console.log('Webhook de teste recebido:', payload.message);
break;
case 'job.completed':
handleJobCompleted(payload);
break;
case 'job.failed':
handleJobFailed(payload);
break;
// Tratar outros eventos...
}

res.status(200).send('OK');
});

function handleJobCompleted(payload) {
const { job_id, external_reference_id } = payload;
const medicalRecord = payload.data.result?.medical_record;

if (medicalRecord) {
console.log(`Job ${job_id} concluído:`, {
recordId: medicalRecord.id,
title: medicalRecord.title,
externalRef: external_reference_id
});

// Processar o registro médico concluído
// O conteúdo HTML completo está disponível em medicalRecord.content
}
}

Payload do Webhook

Estrutura Comum do Payload

Todos os payloads de webhook seguem esta estrutura:

{
"event": "job.completed",
"job_id": "01234567-89ab-cdef-0123-456789abcdef",
"external_reference_id": "seu-id-rastreamento",
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"type": "medical_record_generation",
"status": "completed",
"progress": 100,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z",
"completed_at": "2024-01-15T10:35:00Z",
"external_reference_id": "seu-id-rastreamento",
// Dados específicos do evento...
}
}

Payload de Job Concluído

Quando um job é concluído com sucesso, o payload inclui o registro médico completo:

{
"event": "job.completed",
"job_id": "01234567-89ab-cdef-0123-456789abcdef",
"external_reference_id": "visita-paciente-123",
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"type": "medical_record_generation",
"status": "completed",
"progress": 100,
"created_at": "2024-01-15T10:30:00Z",
"completed_at": "2024-01-15T10:35:00Z",
"external_reference_id": "visita-paciente-123",
"result": {
"medical_record": {
"id": "fedcba98-7654-3210-fedc-ba9876543210",
"user_id": "user-uuid",
"title": "Consulta de Cardiologia",
"content": "<html><body><h1>Registro Médico</h1>...</body></html>",
"template_id": "cardio-template-uuid",
"patient_id": "paciente-123",
"created_at": "2024-01-15T10:35:00Z",
"updated_at": "2024-01-15T10:35:00Z",
"metadata": {
"transcript": "Paciente relata dor no peito...",
"original_transcript": "Paciente relata dor no peito...",
"normalized_transcript": "Paciente relata dor no peito...",
"processing_duration": 45000,
"request_id": "req_abc123",
"external_reference_id": "visita-paciente-123"
}
},
"processing_info": {
"mode": "async",
"duration_ms": 45000,
"template_used": "cardio-template-uuid",
"job_id": "01234567-89ab-cdef-0123-456789abcdef"
}
}
}
}

Payload de Job com Falha

Quando um job falha, o payload inclui detalhes do erro:

{
"event": "job.failed",
"job_id": "01234567-89ab-cdef-0123-456789abcdef",
"external_reference_id": "visita-paciente-123",
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"type": "medical_record_generation",
"status": "failed",
"progress": 50,
"created_at": "2024-01-15T10:30:00Z",
"completed_at": "2024-01-15T10:35:00Z",
"external_reference_id": "visita-paciente-123",
"error": {
"error": "Processamento de áudio falhou: Formato de áudio inválido",
"timestamp": "2024-01-15T10:35:00Z",
"worker_id": "worker-abc123"
}
}
}

Segurança

Verificação de Assinatura HMAC

Todas as requisições de webhook incluem uma assinatura HMAC-SHA256 no header X-Webhook-Signature:

X-Webhook-Signature: sha256=a8b7c6d5e4f3g2h1...

Sempre verifique as assinaturas para garantir que as requisições são da TranscriMed:

import hmac
import hashlib

def verify_webhook_signature(payload_body, signature_header, webhook_secret):
"""Verificar assinatura do webhook"""
expected_signature = hmac.new(
webhook_secret.encode('utf-8'),
payload_body,
hashlib.sha256
).hexdigest()

received_signature = signature_header.replace('sha256=', '')

return hmac.compare_digest(expected_signature, received_signature)

Melhores Práticas

  1. Sempre verifique assinaturas antes de processar payloads
  2. Use endpoints HTTPS para URLs de webhook
  3. Implemente idempotência - o mesmo payload pode ser enviado múltiplas vezes
  4. Responda rapidamente - processe webhooks de forma assíncrona se necessário
  5. Retorne HTTP 200 para processamento bem-sucedido
  6. Registre eventos de webhook para depuração e monitoramento
  7. Rotacione secrets de webhook regularmente

Entrega e Tentativas

Mecanismo de Entrega

  • Webhooks são entregues via HTTP POST para sua URL configurada
  • Content-Type: application/json
  • User-Agent: TranscriMed-Webhook/1.0
  • Timeout: 30 segundos por requisição

Lógica de Tentativas

Se seu endpoint de webhook não responder com HTTP 200:

  1. Tentativa imediata após 1 segundo
  2. Segunda tentativa após 2 segundos
  3. Terceira tentativa após 4 segundos
  4. Sem tentativas para erros 4xx do cliente (exceto 408, 429)

Headers

Cada requisição de webhook inclui estes headers:

Content-Type: application/json
X-Webhook-Signature: sha256=assinatura
X-Webhook-Event: job.completed
X-Webhook-Timestamp: 2024-01-15T10:35:00Z
User-Agent: TranscriMed-Webhook/1.0

Testes

Webhooks de Teste vs Produção

A TranscriMed mantém consistência entre testes de API e testes de webhook:

  • Credenciais de Cliente de Teste: Use URLs de webhook de teste para desenvolvimento e testes
  • Credenciais de Cliente de Produção: Use URLs de webhook de produção para integração ao vivo

Testando com o Portal do Desenvolvedor

O botão de teste de webhook usa testes do lado do servidor com assinaturas HMAC adequadas:

  1. Para Clientes de Teste: Testa sua URL de webhook de teste configurada com assinaturas válidas
  2. Para Clientes de Produção: Testa sua URL de webhook de produção configurada com assinaturas válidas
  3. Segurança: Todas as requisições de teste incluem assinaturas HMAC-SHA256 adequadas usando seu secret configurado
  4. Resultados Abrangentes: Retorna mensagens de erro detalhadas e orientação para solução de problemas

Testes HTTPS Locais

Para testes de webhook de produção, você precisa de endpoints HTTPS. Aqui estão várias abordagens para testes HTTPS locais:

Opção 1: ngrok (Recomendado)

ngrok cria túneis seguros para seu localhost, perfeito para testes de webhook:

# Instalar ngrok
npm install -g ngrok
# ou visite https://ngrok.com/download

# Iniciar seu servidor de webhook local
node test-webhook-server.js

# Em outro terminal, criar túnel HTTPS
ngrok http 3000

# Use a URL HTTPS (ex: https://abc123.ngrok.io) no Portal do Desenvolvedor

Benefícios:

  • HTTPS real com certificados válidos
  • URL pública acessível dos servidores TranscriMed
  • Inspeção de requisições com interface web do ngrok
  • Tier gratuito disponível

Opção 2: LocalTunnel

LocalTunnel fornece funcionalidade de tunelamento similar:

# Instalar localtunnel
npm install -g localtunnel

# Iniciar seu servidor de webhook local
node test-webhook-server.js

# Criar túnel
lt --port 3000 --subdomain nome-sua-app

# Use a URL HTTPS no Portal do Desenvolvedor

Opção 3: SSL Local com mkcert

mkcert cria certificados de desenvolvimento confiáveis localmente:

# Instalar mkcert
brew install mkcert # macOS
# ou visite https://github.com/FiloSottile/mkcert

# Criar CA local
mkcert -install

# Gerar certificados para localhost
mkcert localhost 127.0.0.1 ::1

# Use certificados em seu servidor (veja exemplo abaixo)

Exemplo de servidor HTTPS com mkcert:

const https = require('https');
const fs = require('fs');

const options = {
key: fs.readFileSync('localhost-key.pem'),
cert: fs.readFileSync('localhost.pem')
};

https.createServer(options, (req, res) => {
// Seu código de handler de webhook
}).listen(3000, () => {
console.log('Servidor HTTPS rodando em https://localhost:3000');
});

Nota: Esta abordagem só funciona para testes na mesma máquina, pois os certificados não são publicamente acessíveis.

Opção 4: Cloudflare Tunnel

Cloudflare Tunnel fornece tunelamento seguro gratuito:

# Instalar cloudflared
brew install cloudflared # macOS
# ou visite https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/

# Iniciar túnel
cloudflared tunnel --url http://localhost:3000

# Use a URL HTTPS fornecida

Testando Assinaturas de Webhook Localmente

Servidor de Teste Aprimorado

Use o transcrimed-api-test-server.js aprimorado com verificação HMAC:

# Executar com verificação de assinatura
WEBHOOK_SECRET=seu_webhook_secret node transcrimed-api-test-server.js

# O servidor irá:
# Verificar assinaturas HMAC-SHA256
# Exibir resultados de verificação
# Registrar todos os detalhes do webhook
# Fornecer mensagens de erro úteis

Verificação Manual de Assinatura

Exemplo de código Node.js para verificar assinaturas de webhook:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}

// Em seu handler de webhook:
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

if (!isValid) {
return res.status(401).send('Assinatura inválida');
}

// Processar webhook...
res.json({success: true});
});

Exemplos de Verificação de Assinatura

Aqui estão exemplos completos para verificar assinaturas de webhook em diferentes linguagens de programação:

Node.js (Express)

const express = require('express');
const crypto = require('crypto');
const app = express();

// Middleware para capturar body raw para verificação de assinatura
app.use('/webhooks/transcrimed', express.raw({ type: 'application/json' }));

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}

app.post('/webhooks/transcrimed', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookSecret = process.env.TRANSCRIMED_WEBHOOK_SECRET;

// Verificar assinatura
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).send('Assinatura inválida');
}

const payload = JSON.parse(req.body);

// Processar evento de webhook
console.log('Webhook recebido:', payload.event);

res.json({ success: true });
});

app.listen(3000, () => {
console.log('Servidor de webhook ouvindo na porta 3000');
});

Python (FastAPI)

import hmac
import hashlib
import json
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

received_signature = signature.replace('sha256=', '')

return hmac.compare_digest(expected_signature, received_signature)

@app.post("/webhooks/transcrimed")
async def webhook_handler(request: Request):
signature = request.headers.get('x-webhook-signature')
webhook_secret = os.getenv('TRANSCRIMED_WEBHOOK_SECRET')

if not signature or not webhook_secret:
raise HTTPException(status_code=401, detail="Assinatura ou secret ausente")

body = await request.body()

# Verificar assinatura
if not verify_webhook_signature(body, signature, webhook_secret):
raise HTTPException(status_code=401, detail="Assinatura inválida")

payload = json.loads(body)

# Processar evento de webhook
print(f"Webhook recebido: {payload.get('event')}")

return JSONResponse({"success": True})

PHP (Slim Framework)

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

$app = AppFactory::create();

function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
$receivedSignature = str_replace('sha256=', '', $signature);

return hash_equals($expectedSignature, $receivedSignature);
}

$app->post('/webhooks/transcrimed', function (Request $request, Response $response) {
$signature = $request->getHeaderLine('x-webhook-signature');
$webhookSecret = $_ENV['TRANSCRIMED_WEBHOOK_SECRET'];

if (empty($signature) || empty($webhookSecret)) {
$response->getBody()->write('Assinatura ou secret ausente');
return $response->withStatus(401);
}

$body = $request->getBody()->getContents();

// Verificar assinatura
if (!verifyWebhookSignature($body, $signature, $webhookSecret)) {
$response->getBody()->write('Assinatura inválida');
return $response->withStatus(401);
}

$payload = json_decode($body, true);

// Processar evento de webhook
error_log('Webhook recebido: ' . $payload['event']);

$response->getBody()->write(json_encode(['success' => true]));
return $response->withHeader('Content-Type', 'application/json');
});

$app->run();
?>

Go (Gin Framework)

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

"github.com/gin-gonic/gin"
)

func verifyWebhookSignature(payload []byte, signature string, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := hex.EncodeToString(mac.Sum(nil))

receivedSignature := strings.TrimPrefix(signature, "sha256=")

return hmac.Equal([]byte(expectedSignature), []byte(receivedSignature))
}

func webhookHandler(c *gin.Context) {
signature := c.GetHeader("x-webhook-signature")
webhookSecret := os.Getenv("TRANSCRIMED_WEBHOOK_SECRET")

if signature == "" || webhookSecret == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Assinatura ou secret ausente"})
return
}

body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Falha ao ler body"})
return
}

// Verificar assinatura
if !verifyWebhookSignature(body, signature, webhookSecret) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Assinatura inválida"})
return
}

var payload map[string]interface{}
if err := json.Unmarshal(body, &payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "JSON inválido"})
return
}

// Processar evento de webhook
log.Printf("Webhook recebido: %v", payload["event"])

c.JSON(http.StatusOK, gin.H{"success": true})
}

func main() {
r := gin.Default()
r.POST("/webhooks/transcrimed", webhookHandler)
r.Run(":3000")
}

Testando Verificação de Assinatura

Use nosso servidor de teste aprimorado para validar sua implementação:

  1. Inicie seu servidor de webhook com verificação de assinatura
  2. Execute o servidor de teste com seu webhook secret:
    WEBHOOK_SECRET=seu_secret node scripts/test-webhook-server.js
  3. Configure webhook no Portal do Desenvolvedor com URL do seu servidor
  4. Teste webhook - o servidor de teste verificará a assinatura e mostrará resultados

Problemas Comuns de Assinatura

Problemas de Incompatibilidade de Assinatura:

  • Usando webhook secret errado
  • Modificando body da requisição antes da verificação
  • Codificação incorreta (use bytes raw, não string)
  • Algoritmo HMAC errado (use SHA-256)

Melhores Práticas:

  • Verifique assinaturas ANTES de fazer parse do JSON
  • Use body raw da requisição para cálculo de assinatura
  • Armazene webhook secrets com segurança (variáveis de ambiente)
  • Use funções de comparação timing-safe
  • Registre falhas de verificação de assinatura para depuração

Isso permite testar webhooks localhost durante desenvolvimento ao usar credenciais de cliente de teste.

Para testar seu webhook:

  1. Vá para Webhooks
  2. Configure sua URL de webhook (pode ser localhost para clientes de teste)
  3. Clique em "Testar Webhook"

Guia de Solução de Problemas

Problemas Comuns de Webhook e Soluções

Falhas de Conexão

Problema: NETWORK_ERROR - Conexão recusada ou host inacessível

Soluções:

  1. Verificar status do servidor: Garanta que seu servidor de webhook está rodando
  2. Verificar porta: Confirme que a porta na URL do webhook corresponde ao seu servidor
  3. Verificar firewall: Garanta que nenhum firewall está bloqueando conexões
  4. Desenvolvimento local: Use host.docker.internal em vez de localhost para ambientes containerizados

Exemplo de Correção:

# Em vez de: http://localhost:3000/webhook
# Use para Docker: http://host.docker.internal:3000/webhook

Erros de Resolução DNS

Problema: DNS_ERROR - Não é possível resolver hostname

Soluções:

  1. Verificar domínio: Verifique se o domínio existe e é publicamente acessível
  2. Testar DNS: Use nslookup ou dig para verificar resolução DNS
  3. Usar endereço IP: Tente usar endereço IP em vez de hostname para testes

Exemplo:

# Testar resolução DNS
nslookup seuwebhook.exemplo.com
dig seuwebhook.exemplo.com

Problemas de Certificado SSL/TLS

Problema: SSL_ERROR - Validação de certificado falhou

Soluções:

  1. Verificar certificado: Garanta que o certificado SSL é válido e não expirou
  2. Usar HTTPS: Webhooks de produção devem usar URLs HTTPS
  3. Cadeia de certificados: Verifique se a cadeia completa de certificados está configurada
  4. Endpoints de teste: Para desenvolvimento, use HTTP com credenciais de teste

Verificação Rápida:

# Testar certificado SSL
curl -I https://seuwebhook.exemplo.com/webhook
openssl s_client -connect seuwebhook.exemplo.com:443

Problemas de Timeout

Problema: TIMEOUT_ERROR - Timeout da requisição (>10 segundos)

Soluções:

  1. Otimizar handler: Garanta que o handler do webhook responde em 10 segundos
  2. Processamento assíncrono: Mova operações pesadas para jobs em background
  3. Resposta rápida: Retorne HTTP 200 imediatamente, processe webhook de forma assíncrona
  4. Verificar carga: Monitore carga do servidor e uso de recursos

Exemplo de Handler Rápido:

app.post('/webhook', (req, res) => {
// Responder imediatamente
res.json({ success: true });

// Processar de forma assíncrona
setImmediate(() => {
processWebhookAsync(req.body);
});
});

Problemas de Código de Status HTTP

Problema: HTTP_CLIENT_ERROR (4xx) ou HTTP_SERVER_ERROR (5xx)

Soluções por Código de Status:

StatusProblemaSolução
400Bad RequestVerificar parsing do body da requisição
401Não AutorizadoVerificar lógica de validação de assinatura
404Não EncontradoVerificar caminho da URL do webhook
405Método Não PermitidoGarantir que endpoint aceita requisições POST
500Erro InternoVerificar logs do servidor para erros
502Bad GatewayVerificar configuração de proxy/load balancer
503Serviço IndisponívelVerificar capacidade e saúde do servidor

Problemas de Verificação de Assinatura

Problema: Verificação de assinatura sempre falha

Passos de Debug:

  1. Verificar webhook secret:

    # Verificar se está usando o secret correto
    echo "Seu webhook secret: $WEBHOOK_SECRET"
  2. Registrar assinaturas recebidas vs esperadas:

    console.log('Assinatura recebida:', req.headers['x-webhook-signature']);
    console.log('Assinatura esperada:', expectedSignature);
    console.log('Tamanho do body:', req.body.length);
  3. Verificar uso de body raw:

    // Errado - body foi parseado como JSON
    const signature = generateSignature(JSON.stringify(req.body));

    // Correto - usar buffer de body raw
    const signature = generateSignature(req.body);
  4. Verificar codificação:

    // Garantir codificação consistente
    const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8') // Especificar codificação
    .digest('hex');

Problemas de CORS (Testes no Navegador)

Problema: Erros de CORS ao testar do navegador

Soluções:

  1. Adicionar headers CORS:

    app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Content-Type, X-Webhook-Signature');
    next();
    });
  2. Tratar requisições preflight:

    app.options('/webhook', (req, res) => {
    res.sendStatus(200);
    });

Melhores Práticas de Teste

Desenvolvimento Local

  1. Use credenciais de teste: Sempre use credenciais de cliente de teste para desenvolvimento local
  2. Comece simples: Teste sem verificação de assinatura primeiro, depois adicione segurança
  3. Use servidor de teste: Aproveite nosso transcrimed-api-test-server.js para testes iniciais
  4. Verifique logs: Monitore tanto logs da TranscriMed quanto logs do seu servidor

Testes de Produção

  1. Use HTTPS: Webhooks de produção requerem endpoints seguros
  2. Teste completamente: Use ambiente de staging antes de produção
  3. Monitore erros: Configure rastreamento e alertas de erros
  4. Implemente tentativas: Trate falhas temporárias graciosamente

Testes de Segurança

  1. Verifique assinaturas: Sempre valide assinaturas HMAC em produção
  2. Teste com secrets errados: Garanta que assinaturas inválidas são rejeitadas
  3. Limitação de taxa: Implemente limitação de taxa em endpoints de webhook
  4. Registre eventos de segurança: Rastreie tentativas de autenticação falhadas

Obtendo Ajuda

Se ainda estiver com problemas:

  1. Verifique logs de webhook no Portal do Desenvolvedor
  2. Teste com nosso servidor de teste: node transcrimed-api-test-server.js
  3. Revise mensagens de erro: O teste de webhook fornece informações detalhadas de erro
  4. Entre em contato com suporte: developers@transcrimed.com.br
  5. Verifique logs do seu servidor para verificar se o payload foi recebido

Payload de Teste

O webhook de teste envia este payload simples:

{
"event": "test.ping",
"test_mode": true,
"timestamp": "2024-01-15T10:35:00Z",
"message": "Webhook de teste do Portal do Desenvolvedor TranscriMed",
"client_type": "test"
}

Para Desenvolvimento Local:

  • Use credenciais de cliente de teste do seu Portal do Desenvolvedor
  • Configure URL de webhook de teste para http://localhost:3000/webhook (ou seu servidor local)
  • Clique em "Testar Webhook" para verificar que seu servidor local recebe o payload
  • Seu servidor de webhook deve responder com HTTP 200

Para Testes de Produção:

  • Use credenciais de cliente de produção
  • Configure URL de webhook de produção (deve ser HTTPS)
  • Teste com servidores de staging/produção antes de ir ao vivo

Monitoramento

Logs de Entrega

O Portal do Desenvolvedor mostra logs de entrega para todas as tentativas de webhook:

  • Status: Código de resposta HTTP ou erro
  • Evento: Qual evento disparou o webhook
  • Timestamp: Quando o webhook foi enviado
  • Tentativas: Número de tentativas de entrega
  • Resposta: Body da resposta do seu endpoint

Solução de Problemas

Problemas de Webhook de Teste

Como webhooks de teste são chamados diretamente do seu navegador:

ProblemaSolução
Conexão recusada/Erro de redeVerifique se seu servidor de webhook está rodando e acessível do seu navegador
Erros de CORSConfigure seu servidor para aceitar requisições do domínio do Portal do Desenvolvedor
Timeout durante testeGaranta que seu endpoint de webhook responde rapidamente (menos de 30 segundos)
404 Não EncontradoVerifique o caminho da URL do webhook e método HTTP (deve aceitar POST)

Problemas de Webhook de Produção

Para webhooks de produção entregues pelos servidores TranscriMed:

ProblemaSolução
Verificação de assinatura falhaVerifique webhook secret e implementação HMAC
TimeoutsGaranta que seu endpoint responde em 30 segundos
Erros 4xxCorrija URL do endpoint, autenticação ou tratamento de requisição
Webhooks ausentesVerifique configuração de eventos e disponibilidade do endpoint
Erros SSL/TLSVerifique se seu certificado HTTPS é válido e configurado corretamente

Dicas de Desenvolvimento Local

  • Use credenciais de cliente de teste para testes de webhook localhost
  • Garanta que seu servidor local aceita requisições POST
  • Verifique configurações de firewall se a conexão falhar
  • Para problemas de CORS, configure seu servidor para aceitar requisições do navegador
  • Teste com um servidor HTTP simples primeiro para verificar conectividade

Exemplos

Handler de Webhook Completo (Express.js)

const express = require('express');
const crypto = require('crypto');
const app = express();

// Parser de body raw para verificação de assinatura
app.use('/webhooks/transcrimed', express.raw({ type: 'application/json' }));

class WebhookHandler {
constructor(secret) {
this.secret = secret;
}

verifySignature(payload, signature) {
const expectedSignature = crypto
.createHmac('sha256', this.secret)
.update(payload)
.digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}

async handleJobCompleted(payload) {
const { job_id, external_reference_id, data } = payload;
const medicalRecord = data.result?.medical_record;

if (!medicalRecord) {
console.error('Sem registro médico no payload do job concluído');
return;
}

// Salvar registro médico no seu banco de dados
await this.saveMedicalRecord({
id: medicalRecord.id,
title: medicalRecord.title,
content: medicalRecord.content,
patientId: medicalRecord.patient_id,
externalRef: external_reference_id,
metadata: medicalRecord.metadata
});

console.log(`Job ${job_id} concluído - registro médico ${medicalRecord.id} salvo`);
}

async handleJobFailed(payload) {
const { job_id, external_reference_id, data } = payload;
const error = data.error;

// Registrar falha e notificar sistemas relevantes
console.error(`Job ${job_id} falhou:`, error);

// Atualizar seu sistema para refletir a falha
await this.markJobAsFailed(external_reference_id, error);
}

async saveMedicalRecord(record) {
// Implemente sua lógica de salvamento no banco
console.log('Salvando registro médico:', record.title);
}

async markJobAsFailed(externalRef, error) {
// Implemente sua lógica de tratamento de erro
console.log('Marcando job como falhou:', externalRef, error);
}
}

const webhookHandler = new WebhookHandler(process.env.TRANSCRIMED_WEBHOOK_SECRET);

app.post('/webhooks/transcrimed', async (req, res) => {
const signature = req.headers['x-webhook-signature'];

if (!signature) {
return res.status(400).send('Assinatura ausente');
}

if (!webhookHandler.verifySignature(req.body, signature)) {
return res.status(401).send('Assinatura inválida');
}

try {
const payload = JSON.parse(req.body);

switch (payload.event) {
case 'test.ping':
// Webhook de teste do Portal do Desenvolvedor - apenas registrar e responder
console.log('Webhook de teste recebido do Portal do Desenvolvedor:', payload.message);
break;
case 'job.completed':
await webhookHandler.handleJobCompleted(payload);
break;
case 'job.failed':
await webhookHandler.handleJobFailed(payload);
break;
case 'job.started':
console.log(`Job ${payload.job_id} iniciou processamento`);
break;
case 'job.created':
console.log(`Job ${payload.job_id} criado e enfileirado`);
break;
default:
console.log(`Evento desconhecido: ${payload.event}`);
}

res.status(200).send('OK');
} catch (error) {
console.error('Erro ao processar webhook:', error);
res.status(500).send('Erro interno do servidor');
}
});

app.listen(3000, () => {
console.log('Servidor de webhook ouvindo na porta 3000');
});

Próximos Passos

  1. Configure seu endpoint de webhook com verificação de assinatura
  2. Configure webhooks no Portal do Desenvolvedor
  3. Teste sua integração usando o recurso de teste de webhook
  4. Monitore logs de entrega para garantir processamento confiável de webhook
  5. Implemente tratamento de erros para entregas de webhook falhadas

Para mais exemplos e SDKs, visite nossa página de Exemplos.