Receitas
Servidor Node/Express completo
Token, eventos e recebedor de webhook com verificação de assinatura.
import crypto from "node:crypto";
import express from "express";
const API = "https://api.iclubing.com";
const KEY = process.env.ICLUBING_KEY;
const SEGREDO = process.env.ICLUBING_WEBHOOK_SECRET;
const app = express();
async function iclubing(path, body, extra = {}) {
const r = await fetch(API + path, {
method: body ? "POST" : "GET",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", ...extra },
body: body ? JSON.stringify(body) : undefined,
});
if (!r.ok) throw new Error(`iClubing ${r.status}: ${await r.text()}`);
return r.json();
}
// 1) token para o clube abrir logado
app.post("/clube/token", async (req, res) => {
res.json(await iclubing(`/v1/participants/${encodeURIComponent(req.user.id)}/token`, {}));
});
// 2) evento a partir do seu fluxo de negócio
export async function depositoConfirmado(deposito) {
await iclubing("/v1/events", {
id: `dep:${deposito.id}`,
participant: deposito.userId,
type: "deposit.confirmed",
amount: deposito.valor,
});
}
// 3) webhook: corpo CRU para conferir a assinatura
app.post("/webhooks/iclubing", express.raw({ type: "application/json" }), async (req, res) => {
const corpo = req.body.toString("utf8");
const p = Object.fromEntries(String(req.get("X-Clube-Signature")).split(",").map((x) => x.split("=")));
const esperado = crypto.createHmac("sha256", SEGREDO).update(`${p.t}.${corpo}`).digest("hex");
if (esperado !== p.v1 || Math.abs(Date.now() / 1000 - p.t) > 300) return res.sendStatus(401);
const ev = JSON.parse(corpo);
if (ev.type === "reward.delivery_requested") {
const { delivery, reward, participant } = ev.data;
try {
await carteira.creditarPremio(participant.external_id, reward.payload, { ref: delivery.id });
await iclubing(`/v1/deliveries/${delivery.id}/confirm`, { external_ref: delivery.id });
} catch (e) {
await iclubing(`/v1/deliveries/${delivery.id}/fail`, { reason: String(e.message).slice(0, 500) });
}
}
res.sendStatus(200);
});