Mergi la conținut

Operare în producție

Webhooks durabile, fără dublarea operațiilor

Semnătură pe corpul brut, inbox persistent și procesare asincronă în afara requestului HTTP.

Exemple server-side cu SDK-urile oficiale Monedo. Înlocuiește identificatorii și configurația cu resursele propriei instalări. Datele din exemple nu sunt tranzacții reale.

1. Configurează un endpoint pe mediu

În consola dezvoltatorilor selectează numai evenimentele necesare, setează URL-ul HTTPS și păstrează secretul de semnare exclusiv pe server. Cheia API și secretul webhook sunt credențiale diferite. Separă test de live și nu folosi același inbox fără un namespace care identifică aplicația, mediul și endpointul. Evenimentul este o notificare verificată, nu o instrucțiune de a executa orice operație din payload.

2. Verifică și persistă înainte de 2xx

constructWebhookEvent din SDK-ul Commerce verifică formatul comun al semnăturii Monedo, inclusiv timestampul. Exemplul este adaptor-agnostic: tu furnizezi corpul brut, headerul și inboxul persistent. insertOnce nu este o funcție SDK; este interfața către baza ta de date. Ea trebuie să scrie evenimentul și outboxul de procesare atomic. Nu folosi un Set, o coadă în memorie sau un promise lăsat să ruleze după închiderea unei funcții serverless.

webhook.mjs · Node.js / server
import { constructWebhookEvent } from '@monedo/commerce';

// Pass the unmodified raw body, the Monedo-Signature header, and the endpoint's
// signing secret. Mount this before JSON body middleware in your web server.
export async function acceptWebhook({ rawBody, signature, secret, inbox }) {
  let event;
  try {
    event = constructWebhookEvent(rawBody, signature, secret);
  } catch {
    return { status: 400 };
  }
  if (!event || typeof event.id !== 'string' || typeof event.type !== 'string') {
    return { status: 400 };
  }
  // Implement insertOnce with a database UNIQUE constraint on endpoint + event.id.
  // Store and queue in the same transaction (transactional outbox). It must resolve
  // for duplicates and throw for storage outages. Never use an in-memory Set.
  try {
    await inbox.insertOnce(event);
  } catch {
    return { status: 503 };
  }
  return { status: 204 };
}

// A separate durable worker processes the inbox and retries failures.
// Do not call payment capture, transfer, email or any external service here.
// Re-read current resources before state changes: events may arrive out of order.

3. Procesează în worker

  • Răspunde rapid, în bugetul de livrare, fără capturi, transferuri ori email în handlerul webhook.
  • Deduplică prin constrângere unică pe endpoint + event.id, nu doar printr-un SELECT urmat de INSERT.
  • Revendică jobul cu lease și retry persistent; folosește dead-letter pentru eșecuri care necesită intervenție.
  • Recitește resursa financiară înainte de schimbări locale: evenimentele pot veni în ordine inversă.
  • Salvează efectul local și outboxul de fulfillment atomic. Același eveniment poate fi primit de mai multe ori.
  • Un eșec de stocare trebuie să întoarcă non-2xx. Nu confirma un eveniment pe care nu îl vei putea procesa.

4. Debug și replay sigur

Înregistrează event.id, tipul, request ID-ul și rezultatul procesării, fără secrete sau date personale inutile. La replay păstrează deduplicarea activă. Un replay repară transportul sau reia o procesare permisă; nu trebuie să recaptureze o plată ori să dubleze un transfer deja executat. La rotația secretului verifică politica de compatibilitate a endpointului și păstrează fereastra de tranziție explicit, nu dezactiva verificarea semnăturii.