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/litestore

Webhook 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

  1. Always verify signatures before processing webhooks
  2. Use HTTPS endpoints only
  3. Store secrets securely and rotate them regularly
  4. Validate event data before processing

Reliability

  1. Implement idempotency to handle duplicate events
  2. Use retry logic with exponential backoff
  3. Monitor webhook delivery and failure rates
  4. Handle rate limits appropriately

Performance

  1. Process webhooks asynchronously to avoid timeouts
  2. Use queues for high-volume event processing
  3. Batch operations when possible
  4. Monitor processing times and optimize slow handlers

Error Handling

  1. Log all webhook attempts for debugging
  2. Return appropriate HTTP status codes
  3. Implement dead letter queues for failed events
  4. 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.

On this page