Clube Coins para desenvolvedores

Coloque um clube de vantagens dentro do seu app, com a sua marca: seus usuários ganham moedas usando o app e trocam por cupons em parceiros.

Começar

  1. Crie uma chave secreta no console (clube-console.usecoins.tech › Mobilidade › Clube no app › Integração). Ela fica só no seu servidor.
  2. Abra o clube: seu servidor chama POST /v1/sessions e o app abre o launchUrl numa webview.
  3. Envie eventos (ex.: corrida concluída) com POST /v1/events. O clube credita as moedas pelas suas regras.

Endereço base: https://clube-api.usecoins.tech. Toda chamada leva a chave no cabeçalho:

Authorization: Bearer ck_live_…

Teste a chave: GET /v1/ping devolve o clube, o ambiente e o plano.

Chaves

TipoOnde usarO que pode
ck_live_… secretaSó no servidor do appAbrir sessões, enviar eventos, consultar saldo (conforme as permissões da chave)
pk_live_… públicaPode ir dentro do app (SDK)Só ler a marca (GET /v1/config)

Nunca coloque a chave secreta dentro do aplicativo. Quem tiver a chave abre o clube como qualquer usuário. Guardamos só um resumo (hash): a chave inteira aparece uma única vez, ao ser criada. Revogar vale na hora.

Abrir o clube para um usuário

POST/v1/sessions

Permissão sessions:write. O usuário não tem senha: a identidade é a do seu app. O primeiro acesso cria o cadastro dele no clube.

{
  "user": {
    "id": "8812",              // ID do usuário no seu app (obrigatório)
    "audience": "PASSENGER",   // público: PASSENGER, DRIVER ou outro criado no console
    "name": "Paula Souza",     // opcional
    "phone": "+5531999990000"  // opcional
  },
  "startPath": "/app/cupons"   // opcional: tela inicial (começa com /app)
}

Resposta 201:

{
  "launchUrl": "https://clube.usecoins.tech/clube/entrar#t=…",
  "launchToken": "…",
  "expiresAt": "2026-11-07T12:02:00.000Z",
  "user": { "id": "…", "externalUserId": "8812", "audience": "PASSENGER", "created": true }
}

O link vale 2 minutos e uma vez só: peça um novo a cada abertura. A sessão no clube dura 1 dia. O mesmo id com outro audience é outra pessoa (ex.: o passageiro e o motorista).

Webview e avisos para o app

Abra o launchUrl numa webview com JavaScript e cookies ligados. O clube manda avisos para o app como JSON { "source": "clube-coins", "type": … }:

AvisoQuandoO que o app faz
readyClube aberto (path)Esconder o carregando
navigateTroca de tela (path)Opcional (ex.: estatística)
session_expiredLink usado/vencido ou sessão terminouPedir um launchUrl novo e reabrir
closePessoa tocou em "Voltar ao app"Fechar a webview

Como receber

PlataformaCanal
AndroidaddJavascriptInterface(obj, "ClubeCoinsAndroid") com @JavascriptInterface postMessage(String json)
iOS (WKWebView)userContentController.add(handler, name: "clubeCoins")
React Native<WebView onMessage={…} /> (ReactNativeWebView.postMessage)
FlutterJavaScriptChannel(name: 'ClubeCoinsFlutter', …)

Os SDKs (Android primeiro) fazem tudo isso por você: pedem o link ao seu servidor, abrem a webview e tratam os avisos.

Eventos

POST/v1/events

Permissão events:write. Envie quando algo acontece no app. Idempotente: o mesmo type + id nunca credita duas vezes (a repetição responde 200 com "duplicate": true).

{
  "type": "ride.completed",          // ex.: ride.completed, order.delivered
  "id": "OS-10293",                  // ID único do evento no seu app
  "user": { "id": "8812", "audience": "PASSENGER" },
  "amountCents": 2550,               // opcional: valor em centavos
  "distanceMeters": 7300,            // opcional
  "occurredAt": "2026-11-07T11:58:00-03:00",  // opcional
  "data": { "cidade": "BH" }         // opcional: guardado junto
}

Resposta: { "status": "CREDITED" | "NO_REWARD" | "REJECTED", "coins": 25, "reason": null, "duplicate": false, … }.

ride.completed usa as Campanhas de Coins do clube; os outros tipos usam as Regras de ganho (fixo e/ou por R$, valor mínimo, teto por dia).

Saldo do usuário

GET/v1/users/{audience}/{id}/wallet

Permissão users:read. Para mostrar o saldo fora do clube (ex.: no menu do app).

{ "user": { "externalUserId": "8812", "audience": "PASSENGER" }, "balance": 48,
  "coinName": { "singular": "Moeda 53", "plural": "Moedas 53" },
  "expiring": [{ "expiresAt": "2027-02-04T…", "amount": 48 }] }

Marca do clube

GET/v1/config

Funciona com a chave pública. Devolve nome, cores, logo, nome da moeda e públicos — útil para o app pintar o botão de entrada.

Webhooks

Cadastre o endereço (https) e gere o segredo no console. Avisos enviados hoje: coins.earned (moedas creditadas) e webhook.test. Falhas são reenviadas.

Cabeçalhos: Clube-Event e Clube-Signature: t=<unix>,v1=<hmac>, onde hmac = HMAC-SHA256(segredo, t + "." + corpo) em hexadecimal. Confira a assinatura e recuse avisos com t muito antigo.

// Node.js
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const ok = crypto.timingSafeEqual(
  Buffer.from(v1, 'hex'),
  crypto.createHmac('sha256', SEGREDO).update(`${t}.${corpoBruto}`).digest());

Erros e limites

Erros vêm como { "error": "mensagem", "code": "CODIGO" }.

HTTPcodeO que fazer
401INVALID_KEYChave ausente, errada, vencida ou revogada
403CLUB_SUSPENDEDClube suspenso: fale com o Clube Coins
403SECRET_KEY_REQUIRED / SCOPE_MISSINGUse a chave secreta com a permissão certa
400AUDIENCE, START_PATH…Corrija o corpo da chamada
403USER_BLOCKEDUsuário bloqueado no console
429RATE_LIMITEDEspere Retry-After segundos (cabeçalhos RateLimit-*)
429QUOTA_EXCEEDED / MAU_LIMITLimite do plano no mês