Os exemplos enviam um orçamento pago e já seguem as regras de consistência:
- reenviam o lote com os mesmos ids em erro de rede,
429e5xx; - respeitam o
Retry-Afterquando ele vem; - não reenviam erro de conteúdo (
400,401,403).
A chave fica na variável de ambiente CLAB_KEY. Troque a URL pela de produção quando a homologação terminar.
Envio completo#
curl -X POST "https://api-dev.clubelab.com/v1/integrations/events" \
-H "Authorization: Bearer $CLAB_KEY" \
-H "Content-Type: application/json" \
--retry 5 \
-d '{
"schemaVersion": 1,
"events": [{
"id": "orc_991:2026-10-07T13:00:00-03:00",
"type": "deal.upserted",
"occurredAt": "2026-10-07T13:00:00-03:00",
"contact": { "externalId": "pac_88", "name": "Maria Souza", "phone": "47999990000" },
"deal": {
"externalId": "orc_991",
"status": "won",
"amount": 350.00,
"openedAt": "2026-10-05T09:12:00-03:00",
"closedAt": "2026-10-07T13:00:00-03:00"
}
}]
}'// Node.js 18+ (fetch nativo)
const API = "https://api-dev.clubelab.com/v1/integrations/events";
async function enviarEventos(events, tentativas = 5) {
for (let i = 0; i < tentativas; i++) {
let res;
try {
res = await fetch(API, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLAB_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ schemaVersion: 1, events }),
signal: AbortSignal.timeout(30_000),
});
} catch {
// Erro de rede ou timeout: tenta de novo com os mesmos ids.
}
if (res?.ok) return (await res.json()).results;
if (res && res.status !== 429 && res.status < 500) {
throw new Error(`Erro ${res.status}: ${await res.text()}`);
}
const espera = Number(res?.headers.get("retry-after")) || 2 ** i;
await new Promise((r) => setTimeout(r, espera * 1000));
}
throw new Error("Não foi possível enviar depois de várias tentativas");
}
const results = await enviarEventos([
{
id: "orc_991:2026-10-07T13:00:00-03:00",
type: "deal.upserted",
occurredAt: "2026-10-07T13:00:00-03:00",
contact: { externalId: "pac_88", name: "Maria Souza", phone: "47999990000" },
deal: {
externalId: "orc_991",
status: "won",
amount: 350.0,
openedAt: "2026-10-05T09:12:00-03:00",
closedAt: "2026-10-07T13:00:00-03:00",
},
},
]);
for (const r of results) {
if (r.status === "rejected") console.error(`Evento ${r.id} recusado: ${r.reason}`);
}# Python 3.8+ com requests (pip install requests)
import os
import time
import requests
API = "https://api-dev.clubelab.com/v1/integrations/events"
def enviar_eventos(events, tentativas=5):
for i in range(tentativas):
res = None
try:
res = requests.post(
API,
headers={"Authorization": f"Bearer {os.environ['CLAB_KEY']}"},
json={"schemaVersion": 1, "events": events},
timeout=30,
)
except requests.RequestException:
pass # Erro de rede ou timeout: tenta de novo com os mesmos ids.
if res is not None and res.ok:
return res.json()["results"]
if res is not None and res.status_code != 429 and res.status_code < 500:
raise RuntimeError(f"Erro {res.status_code}: {res.text}")
espera = res.headers.get("Retry-After") if res is not None else None
time.sleep(int(espera) if espera else 2 ** i)
raise RuntimeError("Não foi possível enviar depois de várias tentativas")
results = enviar_eventos([
{
"id": "orc_991:2026-10-07T13:00:00-03:00",
"type": "deal.upserted",
"occurredAt": "2026-10-07T13:00:00-03:00",
"contact": {"externalId": "pac_88", "name": "Maria Souza", "phone": "47999990000"},
"deal": {
"externalId": "orc_991",
"status": "won",
"amount": 350.00,
"openedAt": "2026-10-05T09:12:00-03:00",
"closedAt": "2026-10-07T13:00:00-03:00",
},
}
])
for r in results:
if r["status"] == "rejected":
print(f"Evento {r['id']} recusado: {r['reason']}")<?php
// PHP 7.4+ com a extensão curl
const API = 'https://api-dev.clubelab.com/v1/integrations/events';
function enviarEventos(array $events, int $tentativas = 5): array
{
for ($i = 0; $i < $tentativas; $i++) {
$retryAfter = null;
$ch = curl_init(API);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLAB_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['schemaVersion' => 1, 'events' => $events]),
CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$retryAfter) {
if (stripos($linha, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($linha, 12));
}
return strlen($linha);
},
]);
$corpo = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($corpo !== false && $status >= 200 && $status < 300) {
return json_decode($corpo, true)['results'];
}
// $status 0 = erro de rede: tenta de novo com os mesmos ids.
if ($status !== 0 && $status !== 429 && $status < 500) {
throw new RuntimeException("Erro $status: $corpo");
}
sleep($retryAfter ?: 2 ** $i);
}
throw new RuntimeException('Não foi possível enviar depois de várias tentativas');
}
$results = enviarEventos([[
'id' => 'orc_991:2026-10-07T13:00:00-03:00',
'type' => 'deal.upserted',
'occurredAt' => '2026-10-07T13:00:00-03:00',
'contact' => ['externalId' => 'pac_88', 'name' => 'Maria Souza', 'phone' => '47999990000'],
'deal' => [
'externalId' => 'orc_991',
'status' => 'won',
'amount' => 350.00,
'openedAt' => '2026-10-05T09:12:00-03:00',
'closedAt' => '2026-10-07T13:00:00-03:00',
],
]]);
foreach ($results as $r) {
if ($r['status'] === 'rejected') {
error_log("Evento {$r['id']} recusado: {$r['reason']}");
}
}Excluir um orçamento#
JSON
{
"schemaVersion": 1,
"events": [
{
"id": "orc_991:excluido:2026-10-08T10:00:00-03:00",
"type": "deal.deleted",
"occurredAt": "2026-10-08T10:00:00-03:00",
"deal": { "externalId": "orc_991" }
}
]
}O envio é o mesmo dos exemplos acima, trocando a lista de eventos.