Pinnacle-Quoten mit einer Edge Function in Supabase
Antwort: lege den API-Key als Supabase-Secret ab, schreibe eine Edge Function, die GET /v1/drops (oder /v1/odds) aufruft und die Zeilen über den Admin-Client von @supabase/server per Upsert speichert, und lass pg_cron + pg_net sie jede Minute mit einem in Supabase Vault gespeicherten Secret Key aufrufen. Edge Functions sind kurzlebig, das ist also ein REST-Polling-Job, kein WebSocket-Listener. Läuft auf Free (30 s Verzögerung, 2 Sportarten) und ab Hobby in Echtzeit.
1. Tabelle#
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. Secret#
supabase secrets set PINNFEED_KEY=ps_live_your_key
3. Funktion 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 });
}),
};
Die Funktion wird von einem Cron-Job mit einem Secret Key aufgerufen, nicht von einem angemeldeten Nutzer. Schalte daher die JWT-Prüfung der Plattform in supabase/config.toml ab und lass withSupabase den Key prüfen:
[functions.pinnfeed-drops]
verify_jwt = false
Deploy mit supabase functions deploy pinnfeed-drops. Prüfe einmal die Antwort von /v1/drops, um das Zeitformat von movedAt zu bestätigen, bevor du dich auf den Primärschlüssel verlässt.
4. Zeitplan#
Aktiviere die Erweiterungen pg_cron und pg_net. Speichere die Projekt-URL und einen Supabase Secret Key (sb_secret_...) einmalig in Supabase Vault, damit sie nie direkt im Cron-SQL stehen:
select vault.create_secret('https://YOUR_PROJECT.supabase.co', 'project_url');
select vault.create_secret('YOUR_SUPABASE_SECRET_KEY', 'secret_key');
Plane dann den Aufruf, lies beide Werte aus vault.decrypted_secrets und sende den Key im 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
);
$$);
Hinweise:
- Nimm den Secret Key, nicht den Publishable Key. Der Publishable Key ist öffentlich; jeder, der ihn hat, könnte die Funktion auslösen.
apikeystattAuthorization. Die aktuelle Supabase-Doku sendet die neuensb_secret_-Keys im Headerapikey; sie sind keine JWTs, verlass dich für sie also nicht aufAuthorization: Bearer. Nutzt dein Projekt noch den altenservice_role-JWT (Supabase schafft die Legacy-Keys bis Ende 2026 ab), lassverify_jwtan und sende diesen Key inapikeyund inAuthorization: Bearer, ebenfalls aus Vault gelesen.- Grenze von Vault.
pg_netschreibt den entschlüsselten Key innet.http_request_queue, bis die Anfrage gesendet ist; jede Rolle, die sich an deiner Datenbank anmelden kann, könnte ihn in dieser Zeit lesen. Halte Datenbank-Logins knapp. - Der PinnFeed-Key selbst bleibt in den Secrets der Edge Function (Schritt 2), nie im SQL.
Jede Minute sind 1.440 Aufrufe pro Tag: ab Hobby unproblematisch (kein Tageslimit), aber über den 1.000 pro Tag von Free. Nimm auf Free daher */2 * * * *.
Nächste Schritte#
Du brauchst jeden Preis, nicht nur Stürze? Speichere /v1/odds-Snapshots: Postgres-Muster. Du willst Push statt Polling? Betreibe einen kleinen, dauerhaft laufenden Worker mit dem WebSocket auf Builder (99 €/Monat). Testphase 3 Tage; eine Karte ist nötig und wird nach Tag 3 belastet, wenn du nicht kündigst. Kostenloser Key.
Bildungsinhalt für Erwachsene (18+). Keine Tipps, kein Gewinnversprechen. In Deutschland nur bei Anbietern auf der GGL-Whitelist wetten.
Häufige Fragen#
Kann eine Edge Function den WebSocket offen halten?#
Nicht zuverlässig; sie leben nur für die Anfrage. Für den Stream nimm einen langlebigen Prozess (eine VM, Fly, Railway, einen Server zu Hause).
Ist der Supabase Secret Key hier sicher?#
Ja, serverseitig: in Vault für den Cron-Aufruf und in der Funktion geprüft. Gib ihn nie an den Browser weiter, und den PinnFeed-Key ebenso wenig.
Kann ich Supabase Realtime nutzen, um Daten an meine App zu pushen?#
Ja: Sobald Zeilen in der Tabelle landen, kann Supabase Realtime die Inserts an dein Frontend senden.