Pular para o conteúdo
Cuidaro Desenvolvedores API v1 · 2026-10-26 OpenAPI Site do Cuidaro

Webhooks

Verificar a assinatura

Esquema igual ao do Stripe: v1 é o HMAC-SHA256 do texto "<t>.<corpo bruto>", com o segredo whsec_… inteiro como chave.

  1. Use o corpo bruto, os bytes exatos recebidos, antes de qualquer parse.
  2. Compare em tempo constante.
  3. Recuse t com mais de 5 minutos de diferença do seu relógio.
  4. Assinatura inválida: responda 400 e não processe.
Verificação

PHP

<?php
function cuidaroSignatureIsValid(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
    $parts = [];
    foreach (explode(',', $header) as $piece) {
        [$key, $value] = array_pad(explode('=', trim($piece), 2), 2, '');
        $parts[$key] = $value;
    }
    if (! isset($parts['t'], $parts['v1']) || ! ctype_digit($parts['t'])) {
        return false;
    }
    if (abs(time() - (int) $parts['t']) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $parts['t'].'.'.$rawBody, $secret);

    return hash_equals($expected, $parts['v1']);
}

$rawBody = file_get_contents('php://input');
if (! cuidaroSignatureIsValid($rawBody, $_SERVER['HTTP_CUIDARO_SIGNATURE'] ?? '', getenv('CUIDARO_WEBHOOK_SECRET'))) {
    http_response_code(400);
    exit;
}
$event = json_decode($rawBody, true);
// grave o evento (deduplicando pelo id) e processe depois
http_response_code(200);

Node.js

const crypto = require('node:crypto');
const express = require('express');

function cuidaroSignatureIsValid(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = {};
  for (const piece of (header || '').split(',')) {
    const i = piece.indexOf('=');
    if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
  }
  if (!/^\d+$/.test(parts.t || '') || !/^[0-9a-f]+$/.test(parts.v1 || '')) return false;
  if (Math.abs(Math.floor(new Date().getTime() / 1000) - Number(parts.t)) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.').update(rawBody).digest();
  const received = Buffer.from(parts.v1, 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}

const app = express();
// express.raw: o corpo chega como Buffer, sem parse — obrigatório para a assinatura
app.post('/webhooks/cuidaro', express.raw({ type: 'application/json' }), (req, res) => {
  if (!cuidaroSignatureIsValid(req.body, req.get('Cuidaro-Signature'), process.env.CUIDARO_WEBHOOK_SECRET)) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body.toString('utf8'));
  // grave o evento (deduplicando pelo id) e processe depois
  res.sendStatus(200);
});

Python

import hashlib, hmac, json, os, time
from flask import Flask, request

app = Flask(__name__)

def cuidaro_signature_is_valid(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = {}
    for piece in (header or "").split(","):
        key, sep, value = piece.strip().partition("=")
        if sep:
            parts[key] = value
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or not v1:
        return False
    if abs(int(time.time()) - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

@app.post("/webhooks/cuidaro")
def cuidaro_webhook():
    raw_body = request.get_data()  # bytes exatamente como chegaram
    if not cuidaro_signature_is_valid(raw_body, request.headers.get("Cuidaro-Signature", ""), os.environ["CUIDARO_WEBHOOK_SECRET"]):
        return "", 400
    event = json.loads(raw_body)
    # grave o evento (deduplicando pelo id) e processe depois
    return "", 200

Referência gerada a partir do contrato OpenAPI 3.1 da API v1 (versão 2026-10-26), conferido contra o código por teste automatizado. Dúvidas: suporte@cuidaro.com.br.