Webhooks
Real-time notifications and integrations with Litestore webhooks
Webhooks Overview
Litestore provides comprehensive webhook support for real-time notifications about important events in your store. Webhooks allow you to integrate Litestore with other services and automate workflows.
Webhook Configuration
Setting Up Webhooks
Configure webhooks in your admin dashboard under Settings > Webhooks:
// Webhook configuration
const webhookConfig = {
url: "https://your-app.com/webhooks/litestore",
secret: "your-webhook-secret", // For signature verification
events: [
"content.submitted",
"content.approved",
"review.created",
"product.purchased",
"commission.earned"
],
active: true,
retryPolicy: {
maxAttempts: 5,
backoffMultiplier: 2,
initialDelay: 1000 // milliseconds
}
}Multiple Endpoints
You can configure multiple webhook endpoints for different purposes:
const webhooks = [
{
name: "Order Processing",
url: "https://api.order-processor.com/webhooks",
events: ["product.purchased", "order.updated"],
secret: "order-secret-123"
},
{
name: "Email Service",
url: "https://api.email-service.com/webhooks",
events: ["user.registered", "review.created"],
secret: "email-secret-456"
},
{
name: "Analytics",
url: "https://api.analytics.com/webhooks",
events: ["*"], // All events
secret: "analytics-secret-789"
}
]Security & Verification
Signature Verification
All webhooks include a cryptographic signature for security:
// Verify webhook signature
import crypto from 'crypto';
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Usage in webhook handler
app.post('/webhooks/litestore', (req, res) => {
const signature = req.headers['x-litestore-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});HTTPS Requirement
Webhook endpoints must use HTTPS for security:
// Valid webhook URLs
const validUrls = [
"https://your-app.com/webhooks/litestore",
"https://api.service.com/hooks/litestore"
];
// Invalid (will be rejected)
const invalidUrls = [
"http://your-app.com/webhooks/litestore", // HTTP not allowed
"http://localhost:3000/webhooks" // Localhost not allowed
];Event Types
Content Events
content.submitted
Fired when creator content is submitted for review:
{
"event": "content.submitted",
"id": "evt_content_submitted_123",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"contentId": "content-456",
"creatorId": "creator-789",
"productIds": ["prod-101", "prod-202"],
"type": "review",
"status": "pending_review",
"media": [
{
"type": "image",
"url": "https://cdn.litestore.com/content/image1.jpg",
"caption": "Before and after results"
}
],
"submittedAt": "2024-01-15T10:30:00Z"
}
}content.approved
Fired when content is approved for publication:
{
"event": "content.approved",
"id": "evt_content_approved_124",
"timestamp": "2024-01-15T11:00:00Z",
"data": {
"contentId": "content-456",
"approvedBy": "admin-999",
"approvedAt": "2024-01-15T11:00:00Z",
"commission": {
"amount": 25.50,
"currency": "USD",
"status": "pending"
}
}
}content.rejected
Fired when content is rejected:
{
"event": "content.rejected",
"id": "evt_content_rejected_125",
"timestamp": "2024-01-15T11:15:00Z",
"data": {
"contentId": "content-456",
"rejectedBy": "moderator-888",
"reason": "inappropriate_content",
"feedback": "Please revise content to meet community guidelines",
"rejectedAt": "2024-01-15T11:15:00Z"
}
}Commerce Events
product.purchased
Fired when a product is purchased:
{
"event": "product.purchased",
"id": "evt_product_purchased_126",
"timestamp": "2024-01-15T14:30:00Z",
"data": {
"orderId": "order-777",
"productId": "prod-123",
"variantId": "variant-456",
"quantity": 1,
"price": {
"amount": 49.99,
"currency": "USD"
},
"customer": {
"id": "user-111",
"email": "customer@example.com"
},
"referrer": {
"type": "creator-content",
"contentId": "content-456",
"creatorId": "creator-789"
},
"purchasedAt": "2024-01-15T14:30:00Z"
}
}order.completed
Fired when an order is fully processed:
{
"event": "order.completed",
"id": "evt_order_completed_127",
"timestamp": "2024-01-15T14:45:00Z",
"data": {
"orderId": "order-777",
"total": {
"amount": 74.98,
"currency": "USD"
},
"items": [
{
"productId": "prod-123",
"quantity": 1,
"price": 49.99
},
{
"productId": "prod-456",
"quantity": 1,
"price": 24.99
}
],
"customer": {
"id": "user-111",
"email": "customer@example.com"
},
"shipping": {
"method": "standard",
"cost": 9.99,
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345",
"country": "US"
}
},
"completedAt": "2024-01-15T14:45:00Z"
}
}Review Events
review.created
Fired when a new review is submitted:
{
"event": "review.created",
"id": "evt_review_created_128",
"timestamp": "2024-01-15T16:00:00Z",
"data": {
"reviewId": "review-333",
"productId": "prod-123",
"userId": "user-111",
"orderId": "order-777",
"rating": 5,
"title": "Amazing product!",
"content": "This product exceeded all my expectations...",
"verified": true,
"photos": ["https://cdn.litestore.com/reviews/photo1.jpg"],
"createdAt": "2024-01-15T16:00:00Z"
}
}review.approved
Fired when a review is approved for publication:
{
"event": "review.approved",
"id": "evt_review_approved_129",
"timestamp": "2024-01-15T16:30:00Z",
"data": {
"reviewId": "review-333",
"approvedBy": "moderator-888",
"approvedAt": "2024-01-15T16:30:00Z"
}
}Creator Events
creator.registered
Fired when a new creator signs up:
{
"event": "creator.registered",
"id": "evt_creator_registered_130",
"timestamp": "2024-01-15T09:00:00Z",
"data": {
"creatorId": "creator-999",
"email": "creator@example.com",
"name": "Sarah Johnson",
"website": "https://sarahbeauty.com",
"verificationStatus": "pending",
"registeredAt": "2024-01-15T09:00:00Z"
}
}commission.earned
Fired when a creator earns commission:
{
"event": "commission.earned",
"id": "evt_commission_earned_131",
"timestamp": "2024-01-15T14:45:00Z",
"data": {
"commissionId": "commission-444",
"creatorId": "creator-789",
"contentId": "content-456",
"productId": "prod-123",
"orderId": "order-777",
"amount": {
"value": 4.99,
"currency": "USD"
},
"rate": 0.1,
"status": "pending",
"earnedAt": "2024-01-15T14:45:00Z"
}
}User Events
user.registered
Fired when a new user account is created:
{
"event": "user.registered",
"id": "evt_user_registered_132",
"timestamp": "2024-01-15T08:30:00Z",
"data": {
"userId": "user-222",
"email": "newuser@example.com",
"name": "John Doe",
"registrationMethod": "email",
"registeredAt": "2024-01-15T08:30:00Z"
}
}Handling Webhooks
Webhook Handler Example
// pages/api/webhooks/litestore.ts or app/api/webhooks/litestore/route.ts
import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// Verify webhook signature
const signature = req.headers['x-litestore-signature'] as string;
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET!;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook based on event type
const { event, data } = req.body;
try {
switch (event) {
case 'content.submitted':
await handleContentSubmitted(data);
break;
case 'product.purchased':
await handleProductPurchased(data);
break;
case 'review.created':
await handleReviewCreated(data);
break;
case 'commission.earned':
await handleCommissionEarned(data);
break;
default:
console.log(`Unhandled event: ${event}`);
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
// Event handlers
async function handleContentSubmitted(data: any) {
// Send notification to moderators
await sendNotification({
to: 'moderators@yourstore.com',
subject: 'New content submitted for review',
body: `Content ${data.contentId} submitted by creator ${data.creatorId}`
});
}
async function handleProductPurchased(data: any) {
// Update inventory
await updateInventory(data.productId, -data.quantity);
// Send order confirmation
await sendOrderConfirmation(data.customer.email, data.orderId);
}
async function handleReviewCreated(data: any) {
// Auto-moderate review
const moderation = await moderateReview(data.content);
if (moderation.approved) {
await approveReview(data.reviewId);
} else {
await flagReviewForModeration(data.reviewId, moderation.reason);
}
}
async function handleCommissionEarned(data: any) {
// Update creator's earnings
await updateCreatorEarnings(data.creatorId, data.amount.value);
// Send notification to creator
await notifyCreator(data.creatorId, `You earned $${data.amount.value} in commission!`);
}Idempotency
Handle duplicate webhooks gracefully:
// Store processed event IDs
const processedEvents = new Set<string>();
async function processWebhook(event: any) {
const eventId = event.id;
if (processedEvents.has(eventId)) {
console.log(`Event ${eventId} already processed`);
return;
}
// Process event
await handleEvent(event);
// Mark as processed
processedEvents.add(eventId);
// Clean up old events (keep last 1000)
if (processedEvents.size > 1000) {
const oldestEvents = Array.from(processedEvents).slice(0, 100);
oldestEvents.forEach(id => processedEvents.delete(id));
}
}Retry Handling
Implement proper retry logic for failed webhooks:
async function sendWebhookWithRetry(webhookUrl: string, payload: any, maxRetries = 5) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Litestore-Webhook/1.0'
},
body: JSON.stringify(payload)
});
if (response.ok) {
return { success: true };
}
// Check if it's a retryable error
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
// Client errors (4xx) are not retried
return { success: false, status: response.status };
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
console.error(`Webhook failed after ${maxRetries} attempts:`, error);
return { success: false, error: error.message };
}
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Testing Webhooks
Local Development
Use ngrok or similar tools for local webhook testing:
# Install ngrok
npm install -g ngrok
# Expose local server
ngrok http 3000
# Configure webhook URL in Litestore admin
# https://abc123.ngrok.io/api/webhooks/litestoreWebhook Testing Tools
Use tools like webhook.site or requestbin for testing:
# Test with curl
curl -X POST https://your-webhook-url.com \
-H "Content-Type: application/json" \
-H "X-Litestore-Signature: signature-here" \
-d '{
"event": "content.submitted",
"id": "test-event-123",
"timestamp": "2024-01-15T10:00:00Z",
"data": {
"contentId": "content-123",
"creatorId": "creator-456"
}
}'Best Practices
Security
- Always verify signatures before processing webhooks
- Use HTTPS endpoints only
- Store secrets securely and rotate them regularly
- Validate event data before processing
Reliability
- Implement idempotency to handle duplicate events
- Use retry logic with exponential backoff
- Monitor webhook delivery and failure rates
- Handle rate limits appropriately
Performance
- Process webhooks asynchronously to avoid timeouts
- Use queues for high-volume event processing
- Batch operations when possible
- Monitor processing times and optimize slow handlers
Error Handling
- Log all webhook attempts for debugging
- Return appropriate HTTP status codes
- Implement dead letter queues for failed events
- Set up alerts for high failure rates
Pro Tip
Start with a single webhook endpoint and expand as your integration needs grow. Always test thoroughly in development before deploying to production.