Odds da Pinnacle no Supabase com uma Edge Function
Resposta: guarde a chave da API como segredo do Supabase, escreva uma Edge Function que chama GET /v1/drops (ou /v1/odds) e faz upsert das linhas com o cliente admin de @supabase/server, e deixe pg_cron + pg_net chamarem a função a cada minuto com uma chave secreta guardada no Supabase Vault. Edge Functions duram pouco, então isto é um job de polling REST, não um listener de WebSocket. Funciona no Free (30 s de atraso, 2 esportes) e em tempo real a partir do Hobby.
1. Tabela#
create table if not exists pinnacle_drops (
event_id bigint, market_id text, outcome text,
price_from numeric, price_to numeric, drop_pct numeric,
moved_at timestamptz, home text, away text,
primary key (market_id, outcome, moved_at)
);
2. Segredo#
supabase secrets set PINNFEED_KEY=ps_live_your_key
3. Função supabase/functions/pinnfeed-drops/index.ts#
import { withSupabase } from 'npm:@supabase/server@1';
// auth: 'secret' accepts only callers that send your project's secret key on the apikey header
export default {
fetch: withSupabase({ auth: 'secret' }, async (_req, ctx) => {
const r = await fetch('https://pinnfeed.io/v1/drops?sport=soccer&minutes=5&min_pct=3&limit=100', {
headers: { 'x-api-key': Deno.env.get('PINNFEED_KEY')! },
});
if (!r.ok) return new Response(await r.text(), { status: r.status });
const { drops } = await r.json();
const rows = drops.map((d: any) => ({
event_id: d.eventId, market_id: d.marketId, outcome: d.outcome,
price_from: d.from, price_to: d.to, drop_pct: d.dropPct,
moved_at: d.movedAt, home: d.home, away: d.away,
}));
const { error } = await ctx.supabaseAdmin.from('pinnacle_drops').upsert(rows, { ignoreDuplicates: true });
return Response.json({ saved: rows.length, error: error?.message ?? null });
}),
};
A função é chamada por um cron com uma chave secreta, não por um usuário logado, então desative a checagem de JWT da plataforma em supabase/config.toml e deixe o withSupabase checar a chave:
[functions.pinnfeed-drops]
verify_jwt = false
Publique com supabase functions deploy pinnfeed-drops. Confira uma vez a resposta de /v1/drops para confirmar o formato de movedAt antes de confiar na chave primária.
4. Agendamento#
Ative as extensões pg_cron e pg_net. Guarde uma única vez no Supabase Vault a URL do projeto e uma chave secreta do Supabase (sb_secret_...), para que elas nunca fiquem no SQL do cron:
select vault.create_secret('https://YOUR_PROJECT.supabase.co', 'project_url');
select vault.create_secret('YOUR_SUPABASE_SECRET_KEY', 'secret_key');
Depois agende a chamada lendo os dois valores de vault.decrypted_secrets e enviando a chave no header apikey:
select cron.schedule('pinnfeed-drops', '* * * * *', $$
select net.http_post(
url := (select decrypted_secret from vault.decrypted_secrets where name = 'project_url')
|| '/functions/v1/pinnfeed-drops',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'apikey', (select decrypted_secret from vault.decrypted_secrets where name = 'secret_key')
),
body := '{}'::jsonb
);
$$);
Notas:
- Use a chave secreta, não a publicável. A chave publicável é pública: qualquer pessoa com ela poderia disparar a função.
apikeyversusAuthorization. A documentação atual do Supabase envia as novas chavessb_secret_emapikey; elas não são JWT, então não dependa deAuthorization: Bearerpara elas. Se o seu projeto ainda usa o JWT legadoservice_role(o Supabase vai descontinuar as chaves legadas até o fim de 2026), mantenhaverify_jwtativado e envie essa chave emapikeye emAuthorization: Bearer, lendo-a do Vault do mesmo jeito.- Limite do Vault. O
pg_netgrava a chave descriptografada emnet.http_request_queueaté enviar a requisição, então qualquer papel que consiga entrar no seu banco poderia lê-la enquanto espera. Restrinja os logins no banco. - A chave do PinnFeed continua nos segredos da Edge Function (passo 2), nunca no SQL.
A cada minuto são 1.440 chamadas por dia: tudo bem a partir do Hobby (sem limite diário), mas acima das 1.000 por dia do Free, então no Free use */2 * * * *.
Próximos passos#
Precisa de todos os preços, não só das quedas? Guarde snapshots de /v1/odds: padrão Postgres. Quer push em vez de polling? Rode um pequeno worker sempre ligado com o WebSocket do Builder (€99/mês). Teste de 3 dias; cartão obrigatório, cobrado após o dia 3 a menos que você cancele. Chave grátis.
Conteúdo educativo para maiores de 18 anos. Sem palpites nem lucro garantido. No Brasil, aposte apenas em operadores autorizados pela SPA/MF.
Perguntas frequentes#
Uma Edge Function consegue manter o WebSocket aberto?#
Não de forma confiável: elas duram o tempo da requisição. Para o stream, use um processo de longa duração (uma VM, Fly, Railway, um servidor em casa).
A chave secreta do Supabase está segura aqui?#
Sim, no servidor: no Vault para a chamada do cron e checada dentro da função. Nunca exponha essa chave, nem a do PinnFeed, ao navegador.
Posso usar o Supabase Realtime para enviar dados ao meu app?#
Sim: quando as linhas chegam na tabela, o Supabase Realtime pode transmitir as inserções para o seu front end.