Todas as receitas desta página são a mesma forma, porque a Platform API v1 é
somente leitura:
1
Evento entra
A Tribo gera um member.joined, order.paid, post.created… e a Tryno
empurra a entrega assinada para o seu endpoint.
2
Você enriquece (opcional)
O payload traz identificadores, não o registro completo. Se precisar do nome
do membro ou dos detalhes do pedido, busque em /api/v1 com a sua chave.
3
Você empurra para o seu destino
Planilha, CRM, Slack, banco de dados, ferramenta de e-mail — o que for. Esse
passo acontece fora da Tryno.
Não existe escrita de volta na Tryno. A v1 não tem POST, PATCH nem
DELETE: nenhuma automação pode criar posts, adicionar membros, dar pontos,
aprovar vitórias ou alterar pedidos pela API pública. Se um tutorial de
automação promete “ação na Tryno”, ele está descrevendo algo que a v1 não faz.
O fluxo é sempre Tryno → fora.
Não há app oficial da Tryno no Zapier, no Make ou no n8n. Todas as receitas
abaixo usam blocos genéricos: Webhook (para receber) e HTTP Request (para
ler /v1). Isso funciona bem, mas significa que você configura os cabeçalhos
na mão — inclusive a verificação de assinatura, que essas ferramentas não fazem
por você.
Um endpoint por ferramenta, com só os eventos daquele fluxo. Ver
Configurar webhooks.
3
Dispare o `ping` e confirme a assinatura
Nunca ligue uma automação a um receptor que ainda não rejeitou uma
assinatura inválida em teste.
A verificação de assinatura continua sendo sua responsabilidade, mesmo
dentro de uma ferramenta no-code. Uma URL de webhook do Zapier/Make/n8n é
pública: sem verificar x-tryno-signature, qualquer pessoa que descubra a URL
consegue injetar “vendas” e “membros” falsos na sua planilha. Cada receita
abaixo mostra onde encaixar a verificação.
Objetivo: cada order.paid vira uma linha em Google Sheets, com o nome de
quem comprou (que o payload não traz).Você precisa de: escopos orders:read e members:read; um endpoint de
webhook assinado em order.paid.
No Zapier use um passo Code by Zapier; no n8n, um nó Code; no Make,
um módulo de custom function. O código de verificação (Node e Python) está
em Configurar webhooks.
Ferramentas no-code costumam entregar o corpo já convertido em objeto.
Se você não conseguir o corpo bruto exatamente como chegou, a assinatura
não vai bater — e a alternativa correta não é pular a verificação, é
colocar um pequeno receptor próprio (Cloudflare Worker, Vercel Function,
Lambda) entre a Tryno e a ferramenta: ele verifica a assinatura sobre os
bytes originais e só então repassa.
3
Deduplique por x-tryno-delivery
Antes de escrever a linha, cheque se aquele id já está na planilha (uma
coluna delivery_id com busca). Reentregas acontecem: sem isso, você conta a
mesma venda duas vezes.
4
Enriqueça com uma leitura de /v1
O data do order.paid traz orderId, buyerId, offerId, amountCents
e currency — mas não o nome do comprador. Puxe a lista de pedidos e case
pelo id:
Cada item traz id, offerId, buyerUsername, amountCents,
amountCurrency, status e createdAt. Encontre o item cujo id é igual
ao orderId do evento e use o buyerUsername.
Não existe GET /v1/orders/{id}. A busca por um pedido específico é feita
na lista, que vem ordenada por createdAt desc — o pedido recém-pago está
quase sempre na primeira página.
5
Escreva a linha
Colunas úteis: delivery_id, created (do corpo do evento), orderId,
buyerUsername, amountCents / 100, currency, status.
amountCents é inteiro em centavos. Divida por 100 apenas na
formatação. Se a célula da planilha estiver como número, escreva o valor
já dividido; se estiver como texto, cuidado com a vírgula decimal do
pt-BR.
Objetivo: cada member.joined cria ou atualiza um contato no seu CRM, com
nome de exibição e avatar.Você precisa de: escopo members:read; endpoint assinado em member.joined.
1
Receba e verifique
Igual à receita anterior: assinatura primeiro, depois dedupe por
x-tryno-delivery.
2
Descubra o username
O data do member.joined traz userId (e handle da tribo, e às vezes
via: "invite") — não traz username. E /v1/members é indexada por
username, não por userId: não existe busca por userId.O caminho honesto é: chame GET /v1/members?pageSize=100&page=1 logo após
o evento. Como a lista vem ordenada por joinedAtdesc, quem acabou de
entrar está no topo. Case por joinedAt próximo ao created do evento.
O item único traz username, displayName, avatarUrl, bio, role,
points, level e joinedAt. A lista traz o mesmo conjunto sem a
bio.
4
Faça UPSERT no CRM
Chaveie pelo username, não pela posição na lista. Assim uma reentrega ou
uma leitura duplicada não cria contato repetido.
A API não expõe e-mail de membro. Nem a lista, nem o item único, nem o
payload do webhook trazem endereço de e-mail ou telefone. Se a sua automação
precisa disso, ela precisa coletar o contato por outro caminho (formulário,
checkout próprio) — não há como obtê-lo pela Platform API v1.
Quando o objetivo é análise, e não reação, uma varredura periódica é legítima.
1
Rode uma vez por dia, não em laço contínuo
Uma varredura completa de membros com pageSize=100 custa 1 requisição a
cada 100 membros. O laço pronto está em
Paginação.
2
UPSERT, nunca INSERT
A paginação é por offset e a lista muda durante a varredura: itens na
fronteira entre páginas podem repetir. UPSERT por username (membros) ou
id (posts, eventos, pedidos) torna isso inofensivo.
3
Trate o 429 com backoff
Use o cliente de Erros e limites.
120 req/min por chave dá folga para ~12.000 registros por minuto.
4
Combine com webhooks para o intervalo
A varredura diária dá a base consistente; os webhooks cobrem o que aconteceu
entre duas varreduras sem você precisar rodar de hora em hora.
Este é o “adaptador” recomendado quando o destino é uma ferramenta no-code: ele
fica entre a Tryno e o Zapier/n8n, verifica a assinatura sobre o corpo bruto,
deduplica e só então repassa.
Cloudflare Worker / Vercel Edge
const TOLERANCE_SECONDS = 300;async function hmacHex(secret, message) { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message)); return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");}function timingSafeEqual(a, b) { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i); return diff === 0;}export default { async fetch(request, env, ctx) { if (request.method !== "POST") return new Response("method not allowed", { status: 405 }); // Corpo BRUTO — leia uma vez, use para o HMAC e só depois faça parse. const raw = await request.text(); const header = request.headers.get("x-tryno-signature") ?? ""; const parts = Object.fromEntries( header.split(",").map((kv) => { const i = kv.indexOf("="); return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()]; }), ); const t = Number(parts.t); if (!Number.isFinite(t) || !parts.v1) return new Response("bad signature", { status: 401 }); if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) { return new Response("stale", { status: 401 }); } const expected = await hmacHex(env.TRYNO_WEBHOOK_SECRET, `${t}.${raw}`); if (!timingSafeEqual(expected, parts.v1)) { return new Response("bad signature", { status: 401 }); } // Dedupe pelo id estável da entrega (KV com TTL de 7 dias basta). const deliveryId = request.headers.get("x-tryno-delivery") ?? ""; if (await env.SEEN.get(deliveryId)) return new Response(null, { status: 204 }); await env.SEEN.put(deliveryId, "1", { expirationTtl: 604_800 }); // Repasse para a ferramenta no-code fora do caminho da resposta: // responder rápido evita o timeout de 10s e a reentrega. ctx.waitUntil( fetch(env.DOWNSTREAM_URL, { method: "POST", headers: { "content-type": "application/json" }, body: raw, }), ); return new Response(null, { status: 204 }); },};
Repassar o corpo bruto para a ferramenta downstream (em vez de um objeto
re-serializado) mantém o payload idêntico ao que a Tryno enviou. Se você
precisar acrescentar dados de /v1, faça isso no waitUntil, depois de já ter
respondido 204.