Torna al blog

2026-03-08

Cron senza server: GitHub Actions che chiama la tua API (scheduled webhook)

Pattern tecnico: workflow su schedule, POST autenticato verso l’app, limiti di precisione e minuti Actions. In coda: pacchetto Snowinch @snowinch/githubcron che genera i workflow.

Problema che risolvi: hai un’app su Vercel / Netlify / Cloudflare e ti serve un job periodico (report, sync, reminder) senza VPS sempre acceso e senza subito AWS EventBridge.

Cosa fai in ~20 minuti: un workflow GitHub Actions su schedule che fa POST al tuo endpoint con un secret condiviso.

Come sai che funziona: nella tab Actions vedi run verdi e nei log del server la richiesta autenticata.

Il pattern “scheduled webhook”

  1. GitHub esegue il workflow all’ora definita (cron in UTC — vedi Events that trigger workflows).
  2. Un solo step fa curl (o fetch in Node) verso https://tua-app.example/api/cron/....
  3. L’app verifica header tipo Authorization: Bearer … confrontando con CRON_SECRET in env.
  4. L’handler esegue la logica (DB, email, ecc.) e risponde 200.

La logica resta nell’app: Actions è solo un sveglia affidabile quanto basta per molti casi d’uso interni.

Workflow minimale (YAML)

Salva in .github/workflows/cron-daily.yml (adatta URL e nome segreto):

name: daily-cron-webhook
on:
  schedule:
    - cron: '0 9 * * *' # ogni giorno 09:00 UTC — verifica fuso vs business
  workflow_dispatch: # permette run manuale dalla UI

jobs:
  call-app:
    runs-on: ubuntu-latest
    steps:
      - name: POST /api/cron/daily
        run: |
          curl -fsS -X POST "${{ secrets.APP_CRON_URL }}" \
            -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}" \
            -H "Content-Type: application/json" \
            -d '{}'
  • APP_CRON_URL: es. https://www.example.com/api/cron/daily
  • CRON_SECRET: stringa lunga random; stesso valore in env produzione dell’app.

Sicurezza: non loggare il secret; ruotalo se esposto; limita l’endpoint a operazioni idempotenti dove possibile.

Limiti reali (da non sottovalutare)

  1. Jitter e ritardo: schedule non è real-time; GitHub documenta che l’esecuzione può ritardare in periodi di alto carico. Non usare questo pattern per trading, aste o pagamenti con finestra di secondi.
  2. Minuti Actions: i piani gratuiti hanno un budget minuti/mese — conta durata job × frequenza. Un job di 5 min ogni ora può esaurire rapidamente il free tier.
  3. Timeout dell’hosting: il POST deve finire dentro il limite della tua funzione (es. 10s su Vercel Hobby). Se il lavoro è lungo: accoda un job interno (DB/queue) e rispondi subito 202.
  4. Repo pubblico: il file YAML è visibile; i secret no, ma evita di mettere URL con token in chiaro nel YAML.

Alternative quando il pattern non basta

  • Queue + worker (BullMQ, Cloud Tasks, SQS): per carichi lunghi o retry fine.
  • Cron del provider (Vercel Cron, Cloudflare Triggers): meno pezzi se sei già sul piano giusto.
  • VPS con systemd timer: controllo totale del clock, costo fisso.

Disclosure: @snowinch/githubcron

Snowinch mantiene il pacchetto open source @snowinch/githubcron, che automatizza la creazione di workflow e handler tipizzati (es. integrazione Next.js App Router) partendo dallo stesso pattern sopra. Non è obbligatorio: il YAML manuale è già produzione-ready se preferisci zero dipendenze.

Esempio d’uso libreria (dopo pnpm add @snowinch/githubcron):

import { ServerlessCron } from '@snowinch/githubcron'

export const cron = new ServerlessCron({
  secret: process.env.GITHUBCRON_SECRET,
  baseUrl: process.env.NEXT_PUBLIC_APP_URL,
})

cron.job('send-daily-emails', {
  schedule: '0 9 * * *',
  handler: async () => {
    const result = await sendDailyEmails()
    return { sent: result.length }
  },
})

// app/api/cron/[job]/route.ts — Next.js App Router
export const POST = cron.nextjs.appRouter()

Poi npx githubcron generate per materializzare i file workflow in .github/workflows/.

Sintesi

  1. Implementa POST + secret nell’app.
  2. Aggiungi workflow on.schedule con curl.
  3. Valida con workflow_dispatch prima di affidarti al solo cron.
  4. Valuta @snowinch/githubcron solo se vuoi generare quel boilerplate in modo ripetibile su più progetti.

Non serve promettere “risparmio €X/anno”: il criterio è operativo — il job gira in modo osservabile e dentro i limiti di GitHub e del tuo host.

Vuoi applicare idee come queste al tuo prodotto?

Raccontaci contesto, vincoli e obiettivi: ti diciamo se ha senso lavorare insieme e come impostare il primo passo.