Webhooks

Receba um POST no seu servidor quando algo relevante acontecer. Os eventos de veículo são enviados apenas para organizações que já consultaram aquele veículo — o caso típico é a virada mensal da Tabela FIPE: quando o preço muda, você recebe vehicle.updated com price_changed: true.

EventoQuando
vehicle.enrichedUm veículo foi catalogado pela primeira vez (por exemplo, um item processing do lote)
vehicle.updatedO conteúdo de um veículo mudou (nova revisão) — novo preço FIPE, curadoria ou dado de outra fonte

Cadastro

bash
curl -X POST https://datacar.digitalmart.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $DATACAR_API_KEY" -H "Content-Type: application/json" \
  -d '{ "url": "https://dms.minharevenda.com.br/webhooks/datacar", "events": ["vehicle.updated", "vehicle.enriched"] }'

A resposta traz o secret (whsec_…) uma única vez. A URL precisa ser HTTPS e apontar para um endereço público.

Requisição enviada

http
POST /webhooks/datacar HTTP/1.1
Content-Type: application/json
User-Agent: Datacar-Webhooks/1.0
Datacar-Event: vehicle.updated
Datacar-Delivery: 7f8e4c1a-3b2d-4e5f-9a8b-1c2d3e4f5a6b
Datacar-Signature: t=1798012345,v1=5f2b7c…e91a

{
  "id": "7f8e4c1a-3b2d-4e5f-9a8b-1c2d3e4f5a6b",
  "type": "vehicle.updated",
  "created_at": "2026-10-27T12:00:00.000Z",
  "data": {
    "vehicle_id": "005340-6:2015-3",
    "revision": 4,
    "price_changed": true,
    "vehicle": { "fipeCode": "005340-6", "price": { "month": "2026-11", "amountCents": 9600000 }, "...": "..." }
  }
}

Verificando a assinatura

Calcule o HMAC-SHA256 de <t>.<corpo bruto> com o seu secret e compare com v1 em tempo constante. Rejeite timestamps com mais de 5 minutos de diferença para evitar replay.

Node.jsjs
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest()
  const given = Buffer.from(parts.v1, 'hex')
  return given.length === expected.length && timingSafeEqual(given, expected)
}

// Express: use o corpo BRUTO, não o JSON já parseado.
app.post('/webhooks/datacar', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body.toString('utf8'), req.get('Datacar-Signature') ?? '', process.env.DATACAR_WEBHOOK_SECRET)) {
    return res.sendStatus(400)
  }
  const event = JSON.parse(req.body)
  enfileirar(event) // responda rápido; processe de forma assíncrona
  res.sendStatus(204)
})
PHPphp
function datacar_verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    parse_str(str_replace(',', '&', $header), $parts);
    if (empty($parts['v1']) || abs(time() - (int) $parts['t']) > $tolerance) return false;
    $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}

$raw = file_get_contents('php://input');
if (!datacar_verify($raw, $_SERVER['HTTP_DATACAR_SIGNATURE'] ?? '', getenv('DATACAR_WEBHOOK_SECRET'))) {
    http_response_code(400); exit;
}
$event = json_decode($raw, true);

Entrega e retentativas

Webhooks complementam o feed de alterações, mas não o substituem. Rode o feed periodicamente como rede de segurança.