Passa al contenuto principale

Checkout Hook

Il Checkout Hook è un meccanismo che permette di iniettare logica custom nel processo di checkout prima che l'ordine venga finalizzato e la sessione Stripe venga creata.

Quando un cliente avvia un checkout, Webround raccoglie tutti i dati necessari — prodotti, prezzi, tasse, spedizione, dati cliente — li firma e li invia al tuo endpoint. Tu puoi modificare i line items e gli order items, aggiungere metadati custom, e rispondere. Webround valida la tua risposta e procede con i tuoi dati.


La visione

Il Checkout Hook è pensato per casi d'uso in cui la logica di pricing o di composizione dell'ordine dipende da dati che solo tu conosci: contratti B2B, listini personalizzati per cliente, sconti basati su attributi aziendali, integrazioni con ERP o CRM esterni.

Webround non espone endpoint verso cui inviare dati in anticipo. È Webround a contattare te, al momento giusto nel flusso di checkout. Tu rispondi con i dati modificati, e Webround li usa per creare l'ordine e la sessione di pagamento.

Questo significa che non devi installare nulla su Webround. Ti basta esporre un endpoint HTTPS raggiungibile pubblicamente. Puoi usare qualsiasi tecnologia e ospitarlo dove preferisci.


Configurazione

Il Checkout Hook si configura dal pannello admin di Webround Commerce, nella sezione dedicata.

ParametroDescrizione
URLL'endpoint HTTPS che Webround chiamerà durante il checkout
SecretChiave generata da Webround per firmare le richieste — mostrata una sola volta
TimeoutTempo massimo di attesa in ms (default 5000)
On Errorpassthrough — procede senza modifiche in caso di errore; abort — blocca il checkout
Secret

Il secret viene generato da Webround al momento della creazione e mostrato una sola volta. Conservalo in modo sicuro. Webround non lo mostra mai di nuovo. Non condividerlo con nessuno. Se hai bisogno di supporto, contattaci.


Flusso

Cliente avvia checkout

Webround valida carrello, calcola prezzi, tasse, spedizione, promozioni

Webround firma il payload e chiama il tuo endpoint

Il tuo endpoint valida la firma, elabora, risponde con firma

Webround valida la tua risposta

Webround crea ordine e sessione di pagamento con i tuoi dati

Se il tuo endpoint non risponde entro il timeout o restituisce un errore:

  • Con onError: passthrough → Webround procede con i dati originali, nessuna logica aggiuntiva viene applicata
  • Con onError: abort → il checkout viene bloccato con errore 500

Formato della richiesta

Webround invia una richiesta POST al tuo endpoint con Content-Type: application/json.

Il body contiene:

{
"version": 1,
"storeId": "uuid-dello-store",
"timestamp": 1745000000000,
"signature": "hmac-sha256...",
"store": {
"id": "uuid-dello-store",
"plan": "start",
"countryCode": "IT",
"langCode": "it",
"defaultLangCode": "it"
},
"customer": {
"id": "uuid-del-customer",
"email": "[email protected]",
"isAuthenticated": true,
"emailVerified": true,
"isVatExempt": false,
"vatCode": "IT04917010615",
"displayName": "Mario Rossi",
"phone": null,
"newsletter": true,
"lifetimeValue": { "EUR": { "totalSpend": 1500.00, "totalRefund": 0 } },
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
"addresses": {
"shipping": { "fullName": "Mario Rossi", "countryCode": "IT", "addressLine1": "Via Roma 1", "city": "Roma", "postalCode": "00100", "province": "RM"},
"billing": { "fullName": "Mario Rossi", "countryCode": "IT", "addressLine1": "Via Roma 1", "city": "Roma", "postalCode": "00100", "province": "RM" }
},
"requestsInvoice": false,
"deliveryMethods": { "variant-uuid": "shipping" },
"selectedShippingMethodId": "uuid-metodo-spedizione",
"promotionCodes": [],
"promotionSnapshot": [],
"items": [
{
"variantId": "uuid",
"productId": "uuid",
"priceId": "uuid",
"skuCode": "SKU001",
"quantity": 2,
"currencyCode": "EUR",
"deliveryMethod": "shipping",
"unitNet": 100.00,
"unitTax": 22.00,
"unitGross": 122.00,
"totalNet": 200.00,
"totalTax": 44.00,
"totalGross": 244.00,
"appliedTaxRate": 22,
"taxBehavior": "useWrTax",
"coverUrl": "https://...",
"optionsSnapshot": [],
"stripeProductId": null,
"stripePriceId": null,
"stripeTaxCodeId": null,
"totalStock": 50,
"cadence": "once"
}
],
"shippingOptions": [...],
"lineItems": [...]
}

Sicurezza — Validare la firma in entrata

Ogni richiesta di Webround include una firma HMAC-SHA256 nel body come campo signature.

La firma è calcolata su tutto il body escluso il campo signature stesso:

bodyHash = SHA256(JSON.stringify(body senza signature))
sigPayload = "{version}.{storeId}.{timestamp}.{bodyHash}"
signature = HMAC-SHA256(secret, sigPayload)

Devi sempre validare la firma prima di elaborare la richiesta.

Devi anche validare il timestamp: se la differenza tra il timestamp nella richiesta e l'ora attuale supera i 30 secondi, la richiesta va rifiutata. Questo neutralizza i replay attack — un attaccante che intercetta una richiesta legittima non può re-inviarla dopo che la finestra è scaduta.

function verifyIncoming(body, secret) {
const { signature, ...rest } = body;
const bodyHash = sha256(JSON.stringify(rest));
const sigPayload = `${body.version}.${body.storeId}.${body.timestamp}.${bodyHash}`;
const expected = hmacSha256(secret, sigPayload);
// usa confronto timing-safe per prevenire timing attacks
return timingSafeEqual(expected, signature);
}

const isValid = verifyIncoming(body, secret);
if (!isValid) return Response(401);

const age = Math.abs(Date.now() - body.timestamp);
if (age > 30_000) return Response(401);

Formato della risposta

Webround si aspetta una risposta con Content-Type: application/json e status 200.

Il body deve essere esattamente:

{
"version": 1,
"storeId": "uuid-dello-store",
"timestamp": 1745000000000,
"signature": "hmac-sha256...",
"orderItems": [...],
"lineItems": [...],
"additionalData": {}
}
CampoTipoDescrizione
versionnumberDeve essere 1
storeIdstringLo stesso storeId ricevuto
timestampnumberTimestamp Unix in millisecondi che hai usato per firmare
signaturestringFirma HMAC-SHA256 della tua risposta
orderItemsarrayAlmeno 1 item — vedi schema sotto
lineItemsarrayAlmeno 1 item — line items Stripe compatibili
additionalDataobjectOpzionale — metadati custom salvati sull'ordine

Schema orderItem

{
"variantId": "uuid",
"productId": "uuid",
"priceId": "uuid",
"skuCode": "SKU001",
"quantity": 2,
"unitNet": 90.00,
"unitTax": 19.80,
"unitGross": 109.80,
"totalNet": 180.00,
"totalTax": 39.60,
"totalGross": 219.60,
"appliedTaxRate": 22,
"taxBehavior": "useWrTax",
"deliveryMethod": "shipping",
"coverUrl": null,
"optionsSnapshot": [],
"stripeProductId": null,
"stripePriceId": null,
"stripeTaxCodeId": null
}
Vincoli di validazione
  • variantId deve appartenere al catalogo del tuo store — variantId non riconosciuti causano validation_failed
  • Tutti gli importi nei lineItems devono essere >= 0 — importi negativi causano validation_failed
  • orderItems e lineItems devono contenere almeno 1 elemento

Sicurezza — Firmare la risposta

Webround valida la tua risposta con la stessa logica con cui tu validi la sua richiesta.

Devi firmare il tuo body di risposta escludendo il campo signature, usando il tuo timestamp (che puoi riusare da quello ricevuto o generarne uno nuovo):

bodyHash = SHA256(JSON.stringify(response senza signature))
sigPayload = "{version}.{storeId}.{timestamp}.{bodyHash}"
signature = HMAC-SHA256(secret, sigPayload)

Se la firma non è valida, o se il tuo timestamp è fuori dalla finestra di 30 secondi, Webround tratterà la risposta come fallita e applicherà il comportamento configurato in onError.


Esempio di implementazione

const HOOK_VERSION = 1;
const TOLERANCE_MS = 30_000;

export default {
async fetch(request, env) {
if (request.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });

const body = await request.json();
const secret = env.CHECKOUT_HOOK_SECRET;

// 1. Valida firma inbound
if (!verifySignature(body, secret)) {
return new Response('Unauthorized', { status: 401 });
}

// 2. Valida timestamp
if (Math.abs(Date.now() - body.timestamp) > TOLERANCE_MS) {
return new Response('Unauthorized', { status: 401 });
}

const { storeId, timestamp, items, lineItems } = body;
const customer = body.customer;

// 3. La tua logica custom
// Esempio: sconto 10% per clienti con partita IVA
if (!customer?.vatCode) {
return respond(body, items, lineItems, secret);
}

const discountedItems = items.map(item => applyDiscount(item, 0.10));
const discountedLineItems = lineItems.map(li => ({
...li,
price_data: {
...li.price_data,
unit_amount: Math.round(li.price_data.unit_amount * 0.90),
}
}));

return respond(body, discountedItems, discountedLineItems, secret, {
vatDiscount: true,
discountApplied: '10%'
});
}
};

function respond(incomingBody, orderItems, lineItems, secret, additionalData) {
const { storeId, timestamp } = incomingBody;
const payload = {
version: HOOK_VERSION,
storeId,
timestamp,
orderItems,
lineItems,
...(additionalData ? { additionalData } : {}),
};
const signature = buildSignature(payload, secret, storeId, timestamp);
return new Response(JSON.stringify({ ...payload, signature }), {
headers: { 'Content-Type': 'application/json' },
});
}

function verifySignature(payload, secret) {
const { signature, ...rest } = payload;
const bodyHash = sha256(JSON.stringify(rest));
const sigPayload = `${HOOK_VERSION}.${payload.storeId}.${payload.timestamp}.${bodyHash}`;
const expected = hmacSha256(secret, sigPayload);
return timingSafeEqual(expected, signature);
}

function buildSignature(payload, secret, storeId, timestamp) {
const { signature: _, ...rest } = payload;
const bodyHash = sha256(JSON.stringify(rest));
const sigPayload = `${HOOK_VERSION}.${storeId}.${timestamp}.${bodyHash}`;
return hmacSha256(secret, sigPayload);
}

Log e monitoraggio

Ogni esecuzione del Checkout Hook viene registrata. Puoi consultare i log nella sezione Checkout Hook del tuo pannello admin Webround Commerce.

Per ogni chiamata sono disponibili:

CampoDescrizione
Statusok, error, timeout, validation_failed
DurataTempo di risposta in millisecondi
Fallback applicatoSe Webround ha usato i dati originali al posto dei tuoi
Request payloadIl payload completo inviato al tuo endpoint
Response payloadLa risposta ricevuta dal tuo endpoint
ErroreMessaggio di errore in caso di fallimento

Casi di errore

ScenarioComportamento
Endpoint non raggiungibileerroronError
Timeout superatotimeoutonError
HTTP non 200erroronError
Firma risposta non validavalidation_failedonError
Timestamp risposta scadutovalidation_failedonError
Schema risposta non validovalidation_failedonError
variantId sconosciutovalidation_failedonError
Importo negativo nel lineItemvalidation_failedonError

Con onError: passthrough, in tutti i casi sopra il checkout procede con i dati originali calcolati da Webround. Con onError: abort, il checkout viene bloccato.

Considerazioni finali

  • Libertà tecnologica: L'endpoint può essere implementato con qualsiasi stack (Node.js, Go, Python, etc.) e ospitato su qualsiasi infrastruttura. L'unico vincolo è il rispetto del protocollo HTTPS, della struttura JSON e dei tempi di risposta.
  • Unicità dell'interlocutore: È consentito un solo hook per store. Questo garantisce che eventuali modifiche ai dati arrivino da un solo endpoint, evitando conflitti tra logiche di pricing concorrenti.
  • Logiche esterne: Il sistema è progettato per integrare dati residenti in CRM, ERP o database proprietari. Webround delega la decisione finale sul prezzo e sulla composizione dell'ordine al tuo sistema, agendo solo come esecutore verso Stripe Checkout.
  • Gestione del fallimento: La scelta tra passthrough e abort permette di bilanciare la continuità del servizio rispetto alla precisione della logica custom.
  • Sicurezza end-to-end: La firma bidirezionale garantisce l'integrità del payload sia in entrata che in uscita e il controllo del timestamp impedisce attacchi di tipo replay.
Logiche di timeout

Il checkout è l'elemento più concreto di un sistema e-commerce. Rallentare drasticamente la risposta del sistema in questa fase può comportare un abbandono del sito e, di conseguenza, dell'ordine. Ti consigliamo di mantenere un timeout sufficientemente largo da consentire eventuali ritardi nella risposta, ma anche di implementare il tuo hook con tecnologie che non impiegano troppo tempo a rispondere.

Se il tuo sistema soffre di cold start, ti consigliamp di tenerlo attivo oppure di utilizzare un sistema che non soffre di queste problematiche, come i Cloudflare Workers che bilanciano la flessibilità serverless con le responsività dell'Edge Computing.