On the wire · 04:54:27 UTC Live 549 · Prematch 14,380 · Markets 56,917 Vol. I · No. 5 · Friday 13 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Tutorial · Cloudflare · REST

Cuotas de Pinnacle en un Cloudflare Worker

Actualizado el · PinnFeed · 2 min de lectura

Respuesta: guarda la clave con wrangler secret put PINNFEED_KEY. El handler fetch del Worker hace de proxy de /v1/odds para tu front end con una caché corta en el edge, así la clave nunca llega al navegador; el handler scheduled se ejecuta con un cron trigger, llama a /v1/drops y publica las bajadas nuevas en un webhook de Discord. Todo es REST, así que funciona desde Free (30 s de retraso).

wrangler.toml#

name = "pinnfeed-worker"
main = "src/index.js"
compatibility_date = "2026-11-01"

[triggers]
crons = ["*/2 * * * *"]
npx wrangler secret put PINNFEED_KEY      # ps_live_...
npx wrangler secret put DISCORD_WEBHOOK   # https://discord.com/api/webhooks/...
npx wrangler deploy

src/index.js#

const API = 'https://pinnfeed.io/v1';

export default {
  async fetch(req, env, ctx) {
    const url = new URL(req.url);
    if (url.pathname !== '/odds') return new Response('not found', { status: 404 });
    const sport = url.searchParams.get('sport') || 'soccer';
    const cache = caches.default;
    const cacheKey = new Request(`${url.origin}/odds?sport=${sport}`);
    let res = await cache.match(cacheKey);
    if (!res) {
      const r = await fetch(`${API}/odds?sport=${sport}&market=moneyline&main=true&limit=50`, {
        headers: { 'x-api-key': env.PINNFEED_KEY },
      });
      res = new Response(r.body, { status: r.status, headers: { 'content-type': 'application/json', 'cache-control': 'max-age=5' } });
      if (r.ok) ctx.waitUntil(cache.put(cacheKey, res.clone()));
    }
    return res;
  },

  async scheduled(_event, env) {
    const r = await fetch(`${API}/drops?sport=soccer&minutes=2&min_pct=5&limit=10`, {
      headers: { 'x-api-key': env.PINNFEED_KEY },
    });
    if (!r.ok) return;
    const { drops } = await r.json();
    if (!drops.length) return;
    const lines = drops.map((d) => `${d.home} v ${d.away}: ${d.market} ${d.outcome} ${d.from} → ${d.to} (${d.dropPct}%)`);
    await fetch(env.DISCORD_WEBHOOK, {
      method: 'POST', headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ content: lines.join('\n').slice(0, 1900) }),
    });
  },
};

Como la ventana del cron y la ventana minutes son ambas de 2, cada bajada se publica más o menos una vez; guarda los ids en KV si necesitas una deduplicación estricta.

Límites que respetar#

Free permite 1 petición/s y 1.000 al día, Hobby 2/s y Builder 10/s. La caché del edge evita que los picos de tráfico se conviertan en llamadas a la API. Para push real, mantén el WebSocket en un servidor siempre encendido con Builder (€99/mes). Prueba de 3 días; se pide tarjeta y se cobra después del día 3 salvo que canceles.

Relacionado: apps de navegador y CORS, bot de Discord, API de cuotas que bajan.

Contenido educativo para mayores de 18 años. Sin pronósticos ni ganancias garantizadas. En España, apuesta solo con operadores con licencia de la DGOJ.

Preguntas frecuentes#

¿Puede un Worker mantener abierto el WebSocket de PinnFeed?#

Un Worker normal vive lo que dura la petición. Un Durable Object puede mantener sockets, pero para el stream es más sencillo un pequeño servidor siempre encendido.

¿La caché del edge sirve cuotas viejas?#

Como mucho durante el max-age que pongas (5 s aquí). Bájalo, o no caches las vistas en vivo.

¿El cron cuenta para mi cuota diaria?#

Sí, cada ejecución es una petición: cada 2 minutos son 720 al día, dentro de las 1.000 de Free.

El plan WebSocket: Builder, 99 €/mes.

Cuotas de Pinnacle en tiempo real por un único WebSocket: los 13 deportes, todos los mercados, cuotas justas sin margen y cuotas de cierre. 3 días gratis y cancelas con un clic.

Form K-1 · Free API key

Get your free API key.

One field. No password, no card: enter your email, click the link we send, and your key is on screen.

By continuing you agree to the Terms and Privacy Policy.

One free key per email. Link valid for 20 minutes.