Webhook Integration Guide
This guide covers how to set up, receive, and process webhook events from VesuvioPay to keep your application in sync with real-time events.
Overview​
VesuvioPay webhooks enable real-time notifications when events occur in your store:
- Cart events (item added, updated, removed, cleared)
- Order events (created, status updated, shipped, delivered)
- Product events (created, updated, inventory changed)
- Customer events (created, updated, deleted)
How Webhooks Work​
sequenceDiagram
participant VS as VesuvioPay
participant YS as Your Server
VS->>YS: POST /webhook (Event Payload)
Note over YS: Verify Signature
Note over YS: Process Event
YS-->>VS: 200 OK
alt Delivery Failed
VS->>YS: Retry (Attempt 2)
YS-->>VS: 200 OK
end
- An event occurs in VesuvioPay (e.g., order created)
- VesuvioPay sends an HTTP POST request to your webhook endpoint
- Your server verifies the webhook signature
- Your server processes the event
- Your server responds with HTTP 200
- If delivery fails, VesuvioPay retries with exponential backoff
Webhook Events​
Cart Events​
| Event Type | Description |
|---|---|
cart.created | New cart created |
cart.item_added | Item added to cart |
cart.item_updated | Cart item quantity updated |
cart.item_removed | Item removed from cart |
cart.abandoned | Cart abandoned (no activity) |
cart.recovered | Abandoned cart recovered |
cart.cleared | All items removed from cart |
cart.checkout_started | Checkout process started |
Order Events​
| Event Type | Description |
|---|---|
order.created | New order created (after checkout) |
order.status_updated | Order status changed |
order.confirmed | Order payment confirmed |
order.shipped | Order shipped to customer |
order.delivered | Order delivered |
order.cancelled | Order cancelled |
order.refunded | Order refunded |
order.partially_shipped | Some items shipped |
order.partially_delivered | Some items delivered |
Product Events​
| Event Type | Description |
|---|---|
product.created | New product created |
product.updated | Product details updated |
product.deleted | Product deleted |
product.inventory_updated | Inventory quantity changed |
product.variant_created | Product variant created |
product.variant_updated | Variant details updated |
product.variant_deleted | Variant deleted |
Customer Events​
| Event Type | Description |
|---|---|
customer.created | New customer registered |
customer.updated | Customer profile updated |
customer.deleted | Customer account deleted |
System Events​
| Event Type | Description |
|---|---|
webhook.test | Test webhook event |
webhook.endpoint_created | Webhook endpoint created |
webhook.endpoint_updated | Webhook endpoint updated |
webhook.endpoint_deleted | Webhook endpoint deleted |
Step 1: Create a Webhook Endpoint​
1.1 Register Your Endpoint​
Endpoint: POST /api/v1/sdk/stores/{storeId}/webhooks
Authentication: Private API Key
async function createWebhookEndpoint(storeId, url, events) {
const response = await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'your-private-api-key'
},
body: JSON.stringify({
url: url,
events: events,
description: 'Main webhook endpoint',
isEnabled: true,
apiVersion: '1.0'
})
}
);
return await response.json();
}
// Usage
const webhook = await createWebhookEndpoint(
'a3f2b1c0-1234-5678-90ab-cdef12345678',
'https://yourdomain.com/webhooks/vesuvio',
[
'cart.item_added',
'cart.item_updated',
'cart.item_removed',
'order.created',
'order.status_updated',
'order.shipped',
'order.delivered'
]
);
console.log('Webhook created:', webhook.data.id);
console.log('Webhook secret:', webhook.data.secret); // Save this securely!
Request Body:
{
"url": "https://yourdomain.com/webhooks/vesuvio",
"events": [
"cart.item_added",
"order.created",
"order.status_updated"
],
"description": "Main webhook endpoint for production",
"isEnabled": true,
"apiVersion": "1.0"
}
Response:
{
"success": true,
"data": {
"id": "b4e3c2d1-2345-6789-01bc-def123456789",
"storeId": "a3f2b1c0-1234-5678-90ab-cdef12345678",
"url": "https://yourdomain.com/webhooks/vesuvio",
"events": ["cart.item_added", "order.created"],
"secret": "whsec_abc123xyz789...", // SAVE THIS SECURELY!
"secretMask": "whsec_...xyz",
"description": "Main webhook endpoint",
"isEnabled": true,
"apiVersion": "1.0",
"createdAt": "2025-10-08T10:00:00Z"
}
}
IMPORTANT: Save the webhook secret securely. You'll need it to verify webhook signatures. The secret is only returned once during creation.
1.2 Manage Webhook Endpoints​
List Webhooks​
const webhooks = await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks`,
{ headers: { 'X-Api-Key': 'your-private-api-key' } }
);
Update Webhook​
await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks/${webhookId}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'your-private-api-key'
},
body: JSON.stringify({
events: ['order.created', 'order.shipped'],
isEnabled: true
})
}
);
Delete Webhook​
await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks/${webhookId}`,
{
method: 'DELETE',
headers: { 'X-Api-Key': 'your-private-api-key' }
}
);
Test Webhook​
const testResult = await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks/${webhookId}/test`,
{
method: 'POST',
headers: { 'X-Api-Key': 'your-private-api-key' }
}
);
Rotate Webhook Secret​
const newSecret = await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks/${webhookId}/rotate-secret`,
{
method: 'POST',
headers: { 'X-Api-Key': 'your-private-api-key' }
}
);
// Save the new secret returned in the response
Step 2: Implement Your Webhook Receiver​
2.1 Webhook Payload Structure​
All webhook events follow this structure:
{
"id": "evt_abc123xyz789",
"type": "order.created",
"apiVersion": "1.0",
"createdAt": "2025-10-08T12:30:00Z",
"livemode": true,
"data": {
// Event-specific data
}
}
Common Fields:
id(string): Unique event identifier (use for idempotency)type(string): Event type (e.g., "order.created")apiVersion(string): API version (currently "1.0")createdAt(datetime): When the event occurredlivemode(boolean): Whether this is a production eventdata(object): Event-specific payload
2.2 Example Event Payloads​
Cart Item Added Event​
{
"id": "evt_cart_item_added_123",
"type": "cart.item_added",
"apiVersion": "1.0",
"createdAt": "2025-10-08T12:30:00Z",
"livemode": true,
"data": {
"storeId": "a3f2b1c0-1234-5678-90ab-cdef12345678",
"customerId": "b4e3c2d1-2345-6789-01bc-def123456789",
"occurredAt": "2025-10-08T12:30:00Z",
"cartId": "c5f4d3e2-3456-7890-12cd-ef1234567890",
"item": {
"id": "d6g5e4f3-4567-8901-23de-f12345678901",
"productVariantId": "e7h6f5g4-5678-9012-34ef-g123456789012",
"externalId": "shopify_12345",
"externalVariantId": "shopify_variant_001",
"productTitle": "Premium T-Shirt",
"variantTitle": "Small / Red",
"sku": "TSHIRT-S-RED",
"quantity": 2,
"unitPrice": 29.99,
"totalPrice": 59.98,
"addedAt": "2025-10-08T12:30:00Z"
},
"cartSummary": {
"cartId": "c5f4d3e2-3456-7890-12cd-ef1234567890",
"itemCount": 1,
"totalQuantity": 2,
"subtotal": 59.98,
"estimatedTax": 4.80,
"estimatedShipping": 5.00,
"estimatedTotal": 69.78,
"status": "Active"
}
}
}
Order Created Event​
{
"id": "evt_order_created_456",
"type": "order.created",
"apiVersion": "1.0",
"createdAt": "2025-10-08T13:00:00Z",
"livemode": true,
"data": {
"storeId": "a3f2b1c0-1234-5678-90ab-cdef12345678",
"customerId": "b4e3c2d1-2345-6789-01bc-def123456789",
"occurredAt": "2025-10-08T13:00:00Z",
"orderId": "f8i7g6h5-6789-0123-45fg-h12345678901",
"orderNumber": "VES-1001",
"checkoutId": "g9j8h7i6-7890-1234-56gh-i12345678901",
"customer": {
"customerId": "b4e3c2d1-2345-6789-01bc-def123456789",
"email": "customer@example.com",
"phone": "+1234567890",
"firstName": "John",
"lastName": "Doe"
},
"items": [
{
"id": "h0k9i8j7-8901-2345-67hi-j12345678901",
"productVariantId": "e7h6f5g4-5678-9012-34ef-g123456789012",
"externalProductId": "shopify_12345",
"externalVariantId": "shopify_variant_001",
"productTitle": "Premium T-Shirt",
"variantTitle": "Small / Red",
"sku": "TSHIRT-S-RED",
"quantity": 2,
"unitPrice": 29.99,
"totalPrice": 59.98
}
],
"shippingAddress": {
"firstName": "John",
"lastName": "Doe",
"address1": "123 Main St",
"city": "New York",
"province": "NY",
"country": "United States",
"countryIsoCode": "US",
"postalCode": "10001",
"phone": "+1234567890"
},
"totals": {
"subtotal": 59.98,
"shippingTotal": 5.00,
"taxTotal": 4.80,
"discountTotal": 0.00,
"totalAmount": 69.78
},
"paymentMethod": {
"method": "credit_card",
"display": "Visa ending in 4242",
"last4": "4242",
"brand": "Visa"
},
"orderStatus": "Confirmed",
"placedAt": "2025-10-08T13:00:00Z"
}
}
Step 3: Verify Webhook Signatures​
CRITICAL: Always verify webhook signatures to ensure requests are from VesuvioPay.
VesuvioPay signs all webhook requests using HMAC-SHA256. The signature is sent in the X-Vesuvio-Signature header.
3.1 Signature Format​
X-Vesuvio-Signature: sha256=<hex-encoded-hash>
3.2 Verification Implementation​
JavaScript/Node.js​
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
// Extract the signature hash
const signatureParts = signature.split('=');
if (signatureParts.length !== 2 || signatureParts[0] !== 'sha256') {
return false;
}
const receivedHash = signatureParts[1];
// Compute expected signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const expectedHash = hmac.digest('hex');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(receivedHash, 'hex'),
Buffer.from(expectedHash, 'hex')
);
}
// Express.js middleware
const express = require('express');
const app = express();
app.post('/webhooks/vesuvio',
express.raw({ type: 'application/json' }), // Important: get raw body
(req, res) => {
const signature = req.headers['x-vesuvio-signature'];
const payload = req.body.toString('utf8'); // Raw body as string
const secret = process.env.VESUVIO_WEBHOOK_SECRET;
// Verify signature
if (!verifyWebhookSignature(payload, signature, secret)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
// Parse the payload after verification
const event = JSON.parse(payload);
// Process the event
processWebhookEvent(event);
// Always respond with 200 quickly
res.status(200).send('OK');
}
);
C# / .NET​
using System;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
public class WebhookController : ControllerBase
{
private readonly string _webhookSecret;
public WebhookController(IConfiguration configuration)
{
_webhookSecret = configuration["VesuvioWebhookSecret"];
}
[HttpPost("webhooks/vesuvio")]
public async Task<IActionResult> HandleWebhook()
{
// Read raw body
using var reader = new StreamReader(Request.Body);
var payload = await reader.ReadToEndAsync();
// Get signature from header
var signature = Request.Headers["X-Vesuvio-Signature"].ToString();
// Verify signature
if (!VerifyWebhookSignature(payload, signature, _webhookSecret))
{
return Unauthorized("Invalid signature");
}
// Parse and process event
var webhookEvent = JsonSerializer.Deserialize<WebhookEvent>(payload);
await ProcessWebhookEvent(webhookEvent);
return Ok();
}
private bool VerifyWebhookSignature(string payload, string signature, string secret)
{
// Extract hash from signature
var parts = signature.Split('=');
if (parts.Length != 2 || parts[0] != "sha256")
{
return false;
}
var receivedHash = parts[1];
// Compute expected signature
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var expectedHash = Convert.ToHexString(hashBytes).ToLower();
// Constant-time comparison
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(receivedHash),
Encoding.UTF8.GetBytes(expectedHash)
);
}
private async Task ProcessWebhookEvent(WebhookEvent webhookEvent)
{
switch (webhookEvent.Type)
{
case "order.created":
await HandleOrderCreated(webhookEvent);
break;
case "cart.item_added":
await HandleCartItemAdded(webhookEvent);
break;
// ... other event types
}
}
}
3.3 Additional Security Headers​
VesuvioPay includes additional headers for verification:
X-Vesuvio-Signature: sha256=abc123...
X-Vesuvio-Event-Id: evt_abc123xyz789
X-Vesuvio-Delivery-Id: delivery_123
X-Vesuvio-Webhook-Id: webhook_456
X-Vesuvio-Timestamp: 1633024800
X-Vesuvio-Attempt: 1
User-Agent: VesuvioPay-Webhooks/2.0
You can use these for additional verification:
- Check
X-Vesuvio-Timestampto reject old requests - Use
X-Vesuvio-Event-Idfor idempotency - Log
X-Vesuvio-Attemptto track retries
Step 4: Process Events​
4.1 Idempotency Handling​
CRITICAL: Always implement idempotency to handle duplicate webhook deliveries.
const processedEvents = new Set(); // In production, use Redis or database
async function processWebhookEvent(event) {
const eventId = event.id;
// Check if we've already processed this event
if (processedEvents.has(eventId)) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
try {
// Process the event
await handleEvent(event);
// Mark as processed
processedEvents.add(eventId);
// In production, store in database:
// await db.webhookEvents.create({ eventId, processedAt: new Date() });
} catch (error) {
console.error(`Error processing event ${eventId}:`, error);
throw error; // Let the webhook retry
}
}
4.2 Event Processing Patterns​
Async Processing with Queue​
const Bull = require('bull');
const webhookQueue = new Bull('webhooks', {
redis: { host: 'localhost', port: 6379 }
});
// Webhook receiver - respond quickly
app.post('/webhooks/vesuvio', async (req, res) => {
const event = req.body;
// Verify signature first
if (!verifyWebhookSignature(...)) {
return res.status(401).send('Invalid signature');
}
// Add to queue for async processing
await webhookQueue.add(event, {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 }
});
// Respond immediately
res.status(200).send('OK');
});
// Queue processor
webhookQueue.process(async (job) => {
const event = job.data;
await processWebhookEvent(event);
});
Event Router Pattern​
const eventHandlers = {
'cart.item_added': handleCartItemAdded,
'cart.item_updated': handleCartItemUpdated,
'cart.item_removed': handleCartItemRemoved,
'order.created': handleOrderCreated,
'order.status_updated': handleOrderStatusUpdated,
'order.shipped': handleOrderShipped,
'order.delivered': handleOrderDelivered,
'product.inventory_updated': handleInventoryUpdated
};
async function processWebhookEvent(event) {
const handler = eventHandlers[event.type];
if (!handler) {
console.warn(`No handler for event type: ${event.type}`);
return;
}
try {
await handler(event.data);
} catch (error) {
console.error(`Error handling ${event.type}:`, error);
throw error;
}
}
// Individual event handlers
async function handleOrderCreated(data) {
console.log('New order created:', data.orderNumber);
// Update inventory
await updateInventory(data.items);
// Notify fulfillment
await notifyFulfillment(data.orderId);
// Send confirmation email
await sendOrderConfirmation(data.customer.email, data);
}
async function handleCartItemAdded(data) {
console.log('Item added to cart:', data.item.productTitle);
// Track analytics
await analytics.track('cart_item_added', {
customerId: data.customerId,
productId: data.item.externalId,
price: data.item.unitPrice,
quantity: data.item.quantity
});
}
async function handleInventoryUpdated(data) {
console.log('Inventory updated:', data.productTitle);
// Sync to external platform
await syncInventoryToShopify(data.externalProductId, data.inventoryQuantity);
}
Step 5: Error Handling and Retry Logic​
5.1 Webhook Delivery Behavior​
VesuvioPay webhook delivery:
- Timeout: 30 seconds
- Retries: Up to 5 attempts
- Retry Schedule: Exponential backoff (1min, 5min, 15min, 1hr, 24hr)
- Success: HTTP 2xx response
- Failure: HTTP 4xx/5xx or timeout
5.2 Best Practices for Reliability​
Respond Quickly​
// Good: Acknowledge immediately, process async
app.post('/webhooks/vesuvio', async (req, res) => {
const event = req.body;
// Verify signature
if (!verifyWebhookSignature(...)) {
return res.status(401).send('Invalid signature');
}
// Queue for processing
await queue.add(event);
// Respond immediately (< 1 second)
res.status(200).send('OK');
// Process asynchronously
// processWebhookEvent(event); // This happens in queue worker
});
// Bad: Process synchronously
app.post('/webhooks/vesuvio', async (req, res) => {
const event = req.body;
// This could take 10+ seconds
await processWebhookEvent(event);
await updateDatabase(event);
await sendEmail(event);
res.status(200).send('OK'); // Too slow!
});
Handle Errors Gracefully​
async function processWebhookEvent(event) {
try {
await handleEvent(event);
} catch (error) {
if (error.code === 'DUPLICATE_EVENT') {
// Already processed, don't retry
console.log('Event already processed');
return;
}
if (error.code === 'TEMPORARY_ERROR') {
// Temporary error, let it retry
throw error;
}
// Log permanent errors but don't retry
console.error('Permanent error processing event:', error);
await logErrorToMonitoring(error, event);
}
}
5.3 Monitoring Webhook Health​
const webhookMetrics = {
received: 0,
processed: 0,
failed: 0,
processingTimeMs: []
};
async function trackWebhookProcessing(event, processFn) {
const startTime = Date.now();
webhookMetrics.received++;
try {
await processFn(event);
webhookMetrics.processed++;
} catch (error) {
webhookMetrics.failed++;
throw error;
} finally {
const duration = Date.now() - startTime;
webhookMetrics.processingTimeMs.push(duration);
// Alert if processing is slow
if (duration > 5000) {
console.warn(`Slow webhook processing: ${duration}ms for ${event.type}`);
}
}
}
// Expose metrics endpoint
app.get('/metrics/webhooks', (req, res) => {
const avgProcessingTime = webhookMetrics.processingTimeMs.length > 0
? webhookMetrics.processingTimeMs.reduce((a, b) => a + b, 0) / webhookMetrics.processingTimeMs.length
: 0;
res.json({
received: webhookMetrics.received,
processed: webhookMetrics.processed,
failed: webhookMetrics.failed,
successRate: webhookMetrics.processed / webhookMetrics.received,
avgProcessingTimeMs: avgProcessingTime
});
});
Step 6: Testing Your Integration​
6.1 Use Webhook Testing Endpoint​
const testWebhook = await fetch(
`https://api.vesuviopay.com/api/v1/sdk/stores/${storeId}/webhooks/${webhookId}/test`,
{
method: 'POST',
headers: { 'X-Api-Key': 'your-private-api-key' },
body: JSON.stringify({ eventType: 'order.created' })
}
);
const result = await testWebhook.json();
console.log('Test result:', result);
6.2 Local Testing with ngrok​
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js
# Create tunnel to localhost
ngrok http 3000
# Use the ngrok URL for your webhook endpoint
# https://abc123.ngrok.io/webhooks/vesuvio
6.3 Webhook Testing Tools​
Use tools like webhook.site for testing:
// Create test webhook pointing to webhook.site
await createWebhookEndpoint(
storeId,
'https://webhook.site/unique-url',
['order.created']
);
// Trigger events and view payloads at webhook.site
Complete Integration Example​
Here's a production-ready webhook receiver:
const express = require('express');
const crypto = require('crypto');
const Bull = require('bull');
const app = express();
const webhookQueue = new Bull('webhooks', {
redis: { host: 'localhost', port: 6379 }
});
// Store for idempotency (use Redis/Database in production)
const processedEvents = new Set();
// Webhook secret from environment
const WEBHOOK_SECRET = process.env.VESUVIO_WEBHOOK_SECRET;
// Signature verification
function verifyWebhookSignature(payload, signature, secret) {
const parts = signature.split('=');
if (parts.length !== 2 || parts[0] !== 'sha256') return false;
const receivedHash = parts[1];
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const expectedHash = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(receivedHash, 'hex'),
Buffer.from(expectedHash, 'hex')
);
}
// Webhook receiver endpoint
app.post('/webhooks/vesuvio',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-vesuvio-signature'];
const payload = req.body.toString('utf8');
// Verify signature
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
// Parse event
const event = JSON.parse(payload);
const eventId = event.id;
// Check idempotency
if (processedEvents.has(eventId)) {
console.log(`Event ${eventId} already processed`);
return res.status(200).send('OK');
}
// Queue for processing
await webhookQueue.add(event, {
jobId: eventId,
attempts: 3,
backoff: { type: 'exponential', delay: 2000 }
});
// Mark as received
processedEvents.add(eventId);
// Respond quickly
res.status(200).send('OK');
}
);
// Event handlers
const eventHandlers = {
'order.created': async (data) => {
console.log('Order created:', data.orderNumber);
// Sync to your system
await db.orders.create({
vesuvioOrderId: data.orderId,
orderNumber: data.orderNumber,
customerId: data.customer.customerId,
totalAmount: data.totals.totalAmount,
status: data.orderStatus
});
// Notify fulfillment
await fulfillmentService.createShipment(data);
// Send confirmation
await emailService.sendOrderConfirmation(data.customer.email, data);
},
'order.shipped': async (data) => {
console.log('Order shipped:', data.orderNumber);
// Update your database
await db.orders.update(
{ vesuvioOrderId: data.orderId },
{
status: 'Shipped',
trackingNumber: data.trackingInfo[0]?.trackingNumber
}
);
// Notify customer
await smsService.sendShippingNotification(
data.customer.phone,
data.trackingInfo
);
},
'cart.item_added': async (data) => {
console.log('Cart item added:', data.item.productTitle);
// Track analytics
await analytics.track('cart_add', {
customerId: data.customerId,
productId: data.item.externalId,
variantId: data.item.externalVariantId,
price: data.item.unitPrice,
quantity: data.item.quantity
});
},
'product.inventory_updated': async (data) => {
console.log('Inventory updated:', data.productTitle);
// Sync back to Shopify
await shopify.updateInventory(
data.externalProductId,
data.externalVariantId,
data.inventoryQuantity
);
}
};
// Queue processor
webhookQueue.process(async (job) => {
const event = job.data;
const handler = eventHandlers[event.type];
if (!handler) {
console.warn(`No handler for event type: ${event.type}`);
return;
}
try {
await handler(event.data);
console.log(`Successfully processed event: ${event.type}`);
} catch (error) {
console.error(`Error processing ${event.type}:`, error);
throw error; // Will retry
}
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// Start server
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Troubleshooting​
Webhook Not Receiving Events​
- Verify endpoint URL is publicly accessible
- Check webhook is enabled
- Confirm events are subscribed
- Test with the test endpoint
Signature Verification Failing​
- Ensure you're using the raw request body
- Verify the webhook secret is correct
- Check for middleware that modifies the body
- Ensure charset encoding is UTF-8
Duplicate Events​
- Implement idempotency using event ID
- Store processed event IDs in database
- Return 200 for already-processed events
Slow Processing​
- Respond with 200 immediately
- Process events asynchronously using queues
- Monitor processing time
- Scale queue workers as needed
Best Practices Summary​
- Always verify signatures before processing
- Respond quickly (< 1 second) with HTTP 200
- Process asynchronously using queues
- Implement idempotency using event IDs
- Handle retries gracefully with exponential backoff
- Monitor webhook health and processing times
- Log all events for debugging and auditing
- Use HTTPS for webhook endpoints
- Rotate secrets periodically for security
- Test thoroughly before going live
Next Steps​
- Integrate Product Sync to sync your catalog
- Set up Cart Integration for shopping flow
- Review Order Management for order handling