Documentação

Integrar

Webhooks

Avisos do clube para o seu sistema: eventos, assinatura e retentativas.

Cadastre um endpoint em Integrações › Webhooks (um para teste, outro para produção). O Studio mostra o segredo whsec_… uma vez e já manda um webhook.test para conferir a URL. Marque os eventos que você usa. Sem nenhum marcado, chegam os de prêmio e entrega (reward.*), referral.qualified e message.requested; os avisos de participante (participant.created, level.up, checkin.completed…) só chegam se estiverem marcados pelo nome.

EventoQuando
reward.delivery_requestedO seu sistema precisa creditar um prêmio (bônus, giros, saldo, cashback). Veja Entregas.
reward.grantedQualquer prêmio concedido (informativo).
reward.delivered / reward.delivery_failedUma entrega foi confirmada / marcada como falha.
participant.createdParticipante novo no clube.
level.up / tier.changedSubiu de nível / mudou de elo.
checkin.completedFez o check-in do dia.
mission.completedCompletou uma missão.
wheel.spunGirou a roleta.
code.redeemedAtivou um código promocional.
store.purchasedTrocou moedas por um prêmio na loja.
cashback.creditedCashback do dia pago.
referral.qualifiedIndicação qualificada.
message.requestedCRM no modo webhook: o clube pede e você envia o e-mail/SMS.

Formato

http
POST /seu/endpoint
Content-Type: application/json
X-Clube-Event: level.up
X-Clube-Delivery: whd_…
X-Clube-Signature: t=1727712000,v1=5f0c…

{
  "id": "evt_…",
  "type": "level.up",
  "created_at": "2026-09-30T18:00:00.000Z",
  "club_id": "club_…",
  "env": "live",
  "data": { "participant": { "id": "part_…", "external_id": "user_123" }, "level": 4, "from": 3 }
}

data sempre traz participant.external_id (o seu ID) nos eventos de participante. Use o id do envelope para não processar o mesmo aviso duas vezes.

Verificar a assinatura

v1 = HMAC-SHA256 com o segredo do endpoint sobre t + "." + corpo cru. Recuse se não bater ou se t tiver mais de 5 minutos. Use o corpo cru, antes de converter o JSON.

Node.js
import crypto from "node:crypto";

export function webhookValido(corpoCru, header, segredo) {
  const p = Object.fromEntries(header.split(",").map((x) => x.split("=")));
  const esperado = crypto.createHmac("sha256", segredo).update(`${p.t}.${corpoCru}`).digest("hex");
  const recente = Math.abs(Date.now() / 1000 - Number(p.t)) < 300;
  return recente && p.v1?.length === esperado.length &&
    crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(p.v1));
}
PHP
$corpo = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_CLUBE_SIGNATURE'] ?? ''), $p);
$esperado = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $corpo, getenv('ICLUBING_WEBHOOK_SECRET'));
if (abs(time() - (int)($p['t'] ?? 0)) > 300 || !hash_equals($esperado, $p['v1'] ?? '')) {
  http_response_code(401); exit;
}
$evento = json_decode($corpo, true);
Python
import hmac, hashlib, time

def webhook_valido(corpo_cru: bytes, header: str, segredo: str) -> bool:
    p = dict(x.split("=", 1) for x in header.split(","))
    esperado = hmac.new(segredo.encode(), p["t"].encode() + b"." + corpo_cru, hashlib.sha256).hexdigest()
    return abs(time.time() - int(p["t"])) < 300 and hmac.compare_digest(esperado, p.get("v1", ""))

Respostas e retentativas

  • Responda 2xx em até 10 s. Processamento demorado: responda 200 e trabalhe numa fila.
  • Sem 2xx, tentamos de novo em 1 min, 5 min, 30 min, 2 h, 12 h e mais três vezes a cada 24 h. Depois disso o envio fica como falhou no Studio, com Reenviar.
  • Eventos que você não usa: responda 200 e ignore.
  • Integrações › Webhooks mostra cada envio com status, tentativas e o erro da última.