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.
| Parametro | Descrizione |
|---|---|
| URL | L'endpoint HTTPS che Webround chiamerà durante il checkout |
| Secret | Chiave generata da Webround per firmare le richieste — mostrata una sola volta |
| Timeout | Tempo massimo di attesa in ms (default 5000) |
| On Error | passthrough — procede senza modifiche in caso di errore; abort — blocca il checkout |
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": {}
}
| Campo | Tipo | Descrizione |
|---|---|---|
version | number | Deve essere 1 |
storeId | string | Lo stesso storeId ricevuto |
timestamp | number | Timestamp Unix in millisecondi che hai usato per firmare |
signature | string | Firma HMAC-SHA256 della tua risposta |
orderItems | array | Almeno 1 item — vedi schema sotto |
lineItems | array | Almeno 1 item — line items Stripe compatibili |
additionalData | object | Opzionale — 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
}
variantIddeve appartenere al catalogo del tuo store — variantId non riconosciuti causanovalidation_failed- Tutti gli importi nei
lineItemsdevono essere>= 0— importi negativi causanovalidation_failed orderItemselineItemsdevono 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:
| Campo | Descrizione |
|---|---|
| Status | ok, error, timeout, validation_failed |
| Durata | Tempo di risposta in millisecondi |
| Fallback applicato | Se Webround ha usato i dati originali al posto dei tuoi |
| Request payload | Il payload completo inviato al tuo endpoint |
| Response payload | La risposta ricevuta dal tuo endpoint |
| Errore | Messaggio di errore in caso di fallimento |
Casi di errore
| Scenario | Comportamento |
|---|---|
| Endpoint non raggiungibile | error → onError |
| Timeout superato | timeout → onError |
| HTTP non 200 | error → onError |
| Firma risposta non valida | validation_failed → onError |
| Timestamp risposta scaduto | validation_failed → onError |
| Schema risposta non valido | validation_failed → onError |
| variantId sconosciuto | validation_failed → onError |
| Importo negativo nel lineItem | validation_failed → onError |
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
passthrougheabortpermette 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.
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.