Exemplo de Receptor

Exemplo de receptor (Node.js + Express)

Implementação completa de um receptor de webhooks da GDREdu em Node.js com Express. Inclui validação de assinatura, idempotência e fila local para processamento assíncrono.

Código completo

// webhook-receiver.js
import express from 'express'
import crypto from 'crypto'

const app = express()

// IMPORTANTE: Use raw body para validar a assinatura corretamente
app.use(express.json({
  limit: '1mb',
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8')
  },
}))

// ============================================
// Armazenamento de webhooks e deliveries
// ============================================
// Substitua por um armazenamento seguro de sua preferência
const WEBHOOKS = new Map() // webhookId → secret
const PROCESSED = new Set() // idempotência

// Configure seus webhooks manualmente ou via endpoint admin
WEBHOOKS.set('default-secret-id', process.env.WEBHOOK_SECRET || 'whsec_coloque_seu_secret_aqui')

// ============================================
// Fila local (substitua por uma fila persistente)
// ============================================
const QUEUE = []

function enqueue (deliveryId, event) {
  QUEUE.push({ deliveryId, event })
  processQueue()
}

async function processQueue () {
  while (QUEUE.length > 0) {
    const { deliveryId, event } = QUEUE.shift()
    try {
      await processEvent(deliveryId, event)
    } catch (err) {
      console.error(`[worker] erro ao processar ${deliveryId}:`, err)
    }
  }
}

// ============================================
// Processamento de evento
// ============================================
async function processEvent (deliveryId, event) {
  // Idempotência: pular se já processamos esta delivery
  if (PROCESSED.has(deliveryId)) {
    console.log(`[skip] ${deliveryId} já processado`)
    return
  }
  PROCESSED.add(deliveryId)

  const { type, data } = event
  console.log(`[event] ${type} — person: ${data.person.name} (${data.person.type})`)

  switch (type) {
    case 'equipment.recognized':
      // Exemplo: salvar no seu banco, enviar push, etc
      await saveToYourDatabase(event)
      break
    default:
      console.warn(`[warn] tipo de evento desconhecido: ${type}`)
  }
}

async function saveToYourDatabase (event) {
  // Substitua pelo seu armazenamento
  console.log(`[db] presença salva: ${event.data.person.name}`)
}

// ============================================
// Validação de assinatura
// ============================================
function verifySignature (rawBody, signatureHeader, secret) {
  if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  if (signatureHeader.length !== expected.length) return false
  return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
}

// ============================================
// Endpoint principal
// ============================================
app.post('/webhooks/gdredu', (req, res) => {
  const sig = req.headers['x-gdredu-signature']
  const deliveryId = req.headers['x-gdredu-delivery-id']
  const event = req.body

  // Obter o secret do webhook (busque do seu banco em produção)
  const secret = WEBHOOKS.get('default-secret-id')

  if (!verifySignature(req.rawBody, sig, secret)) {
    console.warn(`[401] assinatura inválida para delivery ${deliveryId}`)
    return res.status(401).send('Invalid signature')
  }

  // Responde 200 imediatamente e processa em background
  res.status(200).json({ ok: true })

  // Enfileira para processamento assíncrono
  enqueue(deliveryId, event)
})

// ============================================
// Health check
// ============================================
app.get('/health', (req, res) => res.json({ ok: true }))

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`[receiver] escutando em http://localhost:${PORT}/webhooks/gdredu`)
})

Como executar

  1. Configure o secret:

    export WEBHOOK_SECRET="whsec_..."  # o secret que a API retornou
  2. Inicie o receptor:

    node webhook-receiver.js
  3. Exponha para a internet (use ngrok, Cloudflare Tunnel, etc):

    ngrok http 3000
    # Anote a URL HTTPS gerada (ex: https://abc123.ngrok.io)
  4. Cadastre o webhook na API:

    curl -X POST "https://api.gdredu.com/v1/webhooks" \
      -H "Authorization: Bearer gdredu_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://abc123.ngrok.io/webhooks/gdredu",
        "events": ["equipment.recognized"]
      }'
  5. Teste o webhook:

    curl -X POST "https://api.gdredu.com/v1/webhooks/<id>/test" \
      -H "Authorization: Bearer gdredu_live_..." \
      -H "x-branch-id: branch-id"

    Você deve ver o evento chegando no log do receptor.

Múltiplos webhooks (múltiplos secrets)

Em produção, diferentes unidades podem ter webhooks diferentes. Mapeie por webhookId (ou por branchId) e armazene o secret no seu banco:

async function getSecretForDelivery (webhookId) {
  // buscar do banco
  return await db.webhooks.getSecret(webhookId)
}

Armazenamento de deliveries

Para auditoria e debug, salve todas as deliveries recebidas:

await db.deliveries.create({
  id: deliveryId,
  event: event.type,
  receivedAt: new Date(),
  payload: event,
  signatureValid: true,
})


Did this page help you?