Avvisi di pagamento fallito su Stripe: contestazioni, rifiuti e webhook morti

Stripe ti avvisa via e-mail di contestazioni e pagamenti falliti. Come trasformare gli eventi webhook di Stripe in push, avvisi urgenti o telefonate.

Aggiornato

Indice

Stripe sa già quando viene aperta una contestazione, quando il pagamento di un abbonamento fallisce o quando un bonifico rimbalza. Te lo dice via e-mail. Per qualcosa di più rumoroso, registra in Stripe un secondo endpoint webhook che punta all'URL di un canale Echobell, iscrivilo a una manciata di tipi di evento e scegli il tipo di notifica in base alla scadenza attaccata a ciascuno. Contestazioni e avvisi precoci di frode hanno un orologio che gira; un rinnovo fallito di solito no.

Questa guida copre quali eventi di Stripe meritano di interromperti, come collegare Stripe a Echobell in circa cinque minuti, i campi dei template che esistono davvero su ogni oggetto e il compromesso che accetti saltando la verifica della firma.

Gli eventi che meritano davvero un'interruzione

Gli avvisi sui pagamenti sbagliano sempre allo stesso modo: qualcuno si iscrive a payment_intent.succeeded perché fa piacere, il telefono vibra quaranta volte al giorno e sei settimane dopo una notifica di contestazione scorre via senza essere letta. Parti invece dalla scadenza. Se perdere l'evento per otto ore non costa nulla, non deve raggiungerti in otto secondi.

EventoPerché contaTipo consigliato
charge.dispute.createdHai una finestra limitata per rispondere — di solito da 7 a 21 giorni, a seconda del circuito. Se la perdi, perdi automaticamente.Chiamata
radar.early_fraud_warning.createdL'emittente della carta ha segnalato a Stripe che un addebito potrebbe essere fraudolento. Rimborsare prima che diventi una contestazione formale è l'azione che ti resta, e la finestra è breve.Chiamata
payout.failedI soldi incassati da Stripe non arrivano in banca. Tutto ciò che ne dipende — stipendi, calcolo del runway — adesso è sbagliato.Chiamata
invoice.payment_failedChurn involontario. Si risolve da solo abbastanza spesso da rendere la chiamata eccessiva, ma gli account più grandi meritano un'occhiata in giornata.Urgente
customer.subscription.deletedChurn volontario. Vale la pena saperlo oggi, non vale una sveglia.Normale
payment_intent.succeededNon è rotto niente. È proprio questo evento ad allenarti a ignorare gli altri cinque.Niente

I livelli corrispondono ai tre tipi di notifica di Echobell: Normale è un push ordinario, Urgente attraversa la maggior parte delle modalità Full Immersion e Chiamata si presenta come una telefonata in arrivo, quindi suona anche con Non disturbare. Ogni iscritto sceglie il proprio livello canale per canale: la cofondatrice può ricevere le contestazioni come chiamata mentre un collega del supporto le riceve come push.

Cosa ti serve

  • Un account Stripe con accesso alla scheda Webhooks in Workbench
  • Echobell installato (App Store / Google Play)
  • Cinque minuti. Niente server, niente deploy, niente codice — a meno che tu non voglia la verifica della firma, che sta nell'ultima sezione.

Passo 1 — Un canale per tipo di evento

Viene la tentazione di fare un unico canale "Stripe" e mandarci dentro tutto. Non farlo. Template del corpo e link alla dashboard cambiano tra contestazione, fattura e bonifico, perché ognuno trasporta un oggetto diverso — e il senso di tutta questa configurazione è che la notifica ti dica cosa è successo senza aprire nulla.

Crea un canale con il nome dell'evento: Stripe Disputes. Scrivi i template di titolo e corpo perché si leggano al volo sulla schermata di blocco:

Title: 🔴 Dispute opened — {{data.object.reason}}
Body: Amount: {{data.object.amount}} {{data.object.currency}}
Charge: {{data.object.charge}}
Status: {{data.object.status}}

Imposta il template del link nelle impostazioni avanzate, così il record della notifica apre la pagina giusta:

https://dashboard.stripe.com/disputes/{{data.object.id}}

Poi iscriviti con tipo Chiamata e copia l'URL del webhook dalla vista dettaglio del canale. Ha questa forma: https://hook.echobell.one/t/<channel-token>.

Attiva POST Only nelle impostazioni avanzate del canale. Stripe invia sempre in POST, e quell'interruttore fa sì che incollare l'URL in una chat o in una scheda del browser non possa più far scattare un falso avviso di contestazione.

Passo 2 — Punta Stripe sul canale

Nella dashboard di Stripe apri la scheda Webhooks e crea una destinazione eventi:

Clicca Create an event destination, seleziona Your account e lascia la versione API sul valore predefinito del tuo account.

Seleziona esattamente un tipo di evento — charge.dispute.created per questo canale. È Stripe stessa a consigliare di iscriversi solo agli eventi che servono alla tua integrazione; qui in più mantiene onesto il template, perché ogni payload che arriva ha la stessa forma.

Scegli Webhook endpoint come tipo di destinazione e incolla l'URL del canale Echobell.

Salva, poi usa Send test event — oppure stripe trigger charge.dispute.created dalla CLI — e verifica che il telefono squilli.

Ripeti per ogni canale creato. Stripe consente fino a 16 endpoint webhook per account, più che sufficienti per uno per livello di avviso.

Passo 3 — Cosa arriva davvero

Stripe invia l'oggetto Event in JSON. Echobell legge il corpo così com'è, quindi ogni campo è raggiungibile in template e condizioni con la notazione a punti:

{
  "id": "evt_1P...",
  "type": "charge.dispute.created",
  "livemode": true,
  "created": 1757548800,
  "data": {
    "object": {
      "id": "dp_1P...",
      "amount": 4900,
      "currency": "usd",
      "reason": "fraudulent",
      "status": "needs_response",
      "charge": "ch_3P...",
      "evidence_details": { "due_by": 1759449600 }
    }
  }
}

Tre cose di questo payload sorprendono quasi tutti:

Gli importi sono interi nella minima unità di valuta. Un amount di 4900 sono 49,00 $. I template di Echobell interpolano e confrontano valori ma non fanno aritmetica, quindi {{data.object.amount}} rende 4900. O lo etichetti onestamente (Amount: 4900 (cents)), o usi il forwarder dell'ultima sezione per dividere per 100 prima dell'invio.

I timestamp sono secondi Unix. {{data.object.evidence_details.due_by}} viene reso come 1759449600, non come data. Se conta più l'esistenza della scadenza che l'ora esatta, toglilo dal template — la pagina della contestazione la mostra — e lascia lavorare il template del link.

I nomi dei campi cambiano per oggetto. Una contestazione ha amount; una fattura ha amount_due, customer_email, attempt_count e hosted_invoice_url; un bonifico ha failure_message e arrival_date; un avviso precoce di frode ha fraud_type, actionable e charge come semplice ID testuale. Una variabile mancante viene resa come stringa vuota anziché come errore, quindi un template copiato dal canale sbagliato fallisce in silenzio. È questo il motivo pratico di un canale per tipo di evento.

Passo 4 — Filtra con le condizioni, non con la forza di volontà

Le condizioni di canale usano la stessa sintassi delle espressioni dei template, senza le graffe, e vengono valutate prima di ogni consegna.

Quella da mettere su ogni canale Stripe:

livemode == true

Il traffico in modalità test — i tuoi stripe trigger, un collega che smanetta in una sandbox — non arriva più sul telefono. Aggiungila dopo aver verificato che il collegamento funziona, non prima.

Per il canale delle fatture fallite una soglia tiene i conti piccoli fuori dalle tue serate:

livemode == true && data.object.amount_due > 20000

Si legge "oltre 200 $", in centesimi. E se preferisci vedere i tentativi davvero bloccati invece di ogni primo fallimento:

livemode == true && data.object.attempt_count > 1

Se invece hai puntato un endpoint con più tipi di evento su un unico canale, le condizioni li rimettono in ordine:

type == "charge.dispute.created" || type == "payout.failed"

Passo 5 — Tieni il percorso dell'avviso fuori da ciò che si rompe

Questa parte vale più dei template.

Il tuo endpoint webhook di produzione è dove avviene il fulfillment: concede accessi, scrive sul database, manda la ricevuta. È quindi anche l'endpoint che cade quando cade la tua applicazione. Quando succede, Stripe riprova fino a tre giorni con backoff esponenziale e ti manda un'e-mail — e un'e-mail sui webhook non consegnati è identica a ogni altra e-mail di Stripe, ed è per questo che la si trova il lunedì.

La morte silenziosa di un webhook ha cause noiose. Stripe considera un redirect 3xx un fallimento, quindi un endpoint che inizia a rimandare da http a https o ad aggiungere lo slash finale smette di ricevere eventi. Richiede TLS 1.2 o superiore, quindi basta un certificato scaduto o mal configurato. Anche un 403 da una regola WAF aggiunta la settimana scorsa.

Un secondo endpoint puntato dritto su Echobell non condivide nulla di tutto ciò. È un altro URL su un altro host con un altro certificato, e continua a suonare mentre la tua applicazione è a terra. La regola si generalizza: il percorso che ti dice che qualcosa è rotto non dovrebbe passare attraverso la cosa rotta.

Vuoi comunque che il guasto del tuo endpoint sia visibile. Controlla la scheda Event deliveries in Workbench quando qualcosa non torna — mostra Delivered, Pending e Failed per evento, con lo stato HTTP di ogni tentativo. Stripe consente di rinviare un evento fino a 15 giorni dalla dashboard, o 30 con stripe events resend dalla CLI: un buco individuato entro due settimane è recuperabile.

L'URL del canale Echobell è una credenziale bearer: chi ce l'ha può far scattare il canale. Puntarci Stripe direttamente significa che nessuno verifica l'header Stripe-Signature, quindi un URL trapelato è una macchina per falsi avvisi, non una violazione di dati. Tienilo fuori da repository e screenshot, usa Reset Token se sfugge, e leggi la sezione successiva se il compromesso ti dà fastidio.

Facoltativo — Verifica prima la firma

Se vuoi che la firma di Stripe venga davvero verificata e gli importi formattati come denaro, metti davanti un piccolo forwarder. Questo Cloudflare Worker verifica l'evento, risponde subito 200 come chiede Stripe e invia a Echobell un payload piatto:

import Stripe from "stripe";

export default {
  async fetch(request, env, ctx) {
    const stripe = new Stripe(env.STRIPE_SECRET_KEY);
    const body = await request.text();

    let event;
    try {
      event = await stripe.webhooks.constructEventAsync(
        body,
        request.headers.get("stripe-signature"),
        env.STRIPE_WEBHOOK_SECRET,
      );
    } catch {
      return new Response("invalid signature", { status: 400 });
    }

    const invoice = event.data.object;
    ctx.waitUntil(
      fetch(env.ECHOBELL_HOOK_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          customer: invoice.customer_email || invoice.customer,
          amount: (invoice.amount_due / 100).toFixed(2),
          currency: invoice.currency.toUpperCase(),
          attempt: invoice.attempt_count,
          externalLink: invoice.hosted_invoice_url,
        }),
      }),
    );

    return new Response("ok", { status: 200 });
  },
};

Il template dall'altra parte diventa molto più pulito, perché la formattazione è già avvenuta nel codice:

Title: 💳 Payment failed — {{currency}} {{amount}}
Body: Customer: {{customer}}
Attempt #{{attempt}}

externalLink è una variabile speciale: senza template del link impostato, Echobell la usa come link del record di notifica, così la fattura ospitata è a un tocco di distanza.

Nota la forma del compromesso: ora è un pezzo di infrastruttura che può guastarsi a sua volta, esattamente ciò contro cui mette in guardia il passo 5. Una via di mezzo ragionevole è verificare le firme sul canale ad alto volume, dove i falsi avvisi darebbero fastidio, e lasciare il canale delle contestazioni collegato direttamente: lì il costo di uno squillo spurio è uno sguardo perplesso, quello di uno squillo mancato è l'importo contestato.

Cosa questa configurazione non ti dà

  • Nessuna reperibilità a turni né escalation. Tutti gli iscritti a un canale di chiamata squillano insieme. È un pregio in quattro e un problema in quaranta; a quaranta serve una piattaforma di incident management.
  • Nessuna deduplicazione. Stripe non garantisce l'ordine degli eventi e può consegnare lo stesso evento più di una volta. Due squilli per una contestazione sono possibili.
  • Nessun riscontro. Niente registra che un umano l'abbia visto, e niente passa a una seconda persona se nessuno risponde.
  • Nessun avviso "i pagamenti si sono fermati". Stripe emette eventi quando le cose accadono, mai quando smettono. Se il checkout si rompe, non parte alcun evento. Per quello serve un job schedulato dalla tua parte che chiami un canale quando gli addebiti dell'ultima ora sono zero — un dead man's switch basato su cron.

Risoluzione dei problemi

L'evento di test mostra 200 su Stripe ma non è arrivata alcuna notifica. Echobell risponde 200 con un corpo JSON anche quando non consegna — guarda il corpo della risposta nella scheda Event deliveries. success: false con un token di lunghezza valida significa token del canale sbagliato. Se success è true, la causa probabile è una condizione: livemode == true blocca ogni evento di test, per progetto.

Stripe segnala 405 Method Not Allowed. Il canale ha POST Only attivo e qualcosa ha inviato un GET. Stripe usa sempre POST, quindi è un'anteprima di link o una scheda del browser, non Stripe.

La notifica arriva con campi vuoti. Il template punta all'oggetto sbagliato — {{data.object.amount}} su un canale fatture, dove il campo è amount_due. Manda un evento reale, aprilo nella dashboard e leggi il JSON.

Le consegne iniziano a fallire dopo settimane di funzionamento. Controlla il certificato e ogni redirect davanti all'URL. Con l'URL del canale usato direttamente è raro; con un forwarder che hai messo in produzione tu, è il sospetto abituale.

Domande frequenti

Stripe può telefonarmi quando si apre una contestazione?

Da solo no. Stripe avvisa via e-mail, nella dashboard, con l'evento charge.dispute.created e via push se usi l'app Stripe Dashboard. Per uno squillo vero, instrada quell'evento su un canale il cui tipo di iscrizione è Chiamata.

Serve scrivere codice per collegare Stripe a Echobell?

No. Stripe invia JSON a qualsiasi URL HTTPS pubblico, e l'URL di un canale Echobell lo è. Il codice serve solo se vuoi verificare l'header Stripe-Signature o riformattare gli importi.

È sicuro dare a Stripe un URL webhook di terze parti?

È un compromesso consapevole. Il payload inviato da Stripe contiene metadati di cliente e pagamento, ed Echobell non conserva in modo permanente i payload grezzi — la notifica renderizzata resta sul tuo dispositivo. Ciò a cui rinunci è la verifica della firma: chi conosce l'URL può mandarti un falso convincente. Trattalo come una chiave API e usa lo schema del forwarder per tutto ciò che preferisci verificare.

Perché il mio avviso mostra 4900 invece di 49,00 $?

Stripe invia gli importi come interi nella minima unità di valuta, e i template di Echobell non fanno aritmetica. Indica l'unità nel template, oppure dividi per 100 in un forwarder prima dell'invio.

Come evito che gli eventi in modalità test mi sveglino?

Aggiungi la condizione livemode == true al canale. Stripe marca ogni evento sandbox e stripe trigger come livemode: false.

Il mio cofondatore può ricevere gli stessi avvisi senza pagare una postazione?

Sì. Condividi il link del canale; ogni iscritto sceglie il proprio tipo di notifica. Una persona prende le contestazioni come chiamata mentre un'altra le riceve come push normale, e non c'è alcun prezzo per postazione sugli iscritti.

Dovrei impostare avvisi sui pagamenti riusciti?

Solo per poco, e solo finché l'attività è abbastanza piccola da rendere ciascuno ancora un avvenimento. Nel momento in cui la notifica di pagamento riuscito diventa routine, comincia a erodere la tua reazione a quelle che contano — il meccanismo centrale della alert fatigue.

In sintesi

L'intera configurazione è una destinazione eventi Stripe per ogni canale Echobell, una condizione livemode == true e la disciplina di riservare il livello chiamata agli eventi con un orologio attaccato. Contestazioni e avvisi precoci di frode ce l'hanno. Un rinnovo fallito su un piano da 9 $ no, e far finta del contrario è esattamente il modo in cui si finisce per dormire durante quello che ce l'aveva.

Scarica Echobell per iPhone o prendilo su Google Play, crea prima il canale delle contestazioni e lancia un stripe trigger charge.dispute.created prima di affidare a questo percorso qualcosa di serio.


Correlati