Skip to main content

Overview

Webhooks notify your server when events happen in Shoppex. Use them to:
  • Fulfill orders automatically
  • Update your database
  • Send custom notifications
  • Integrate with external services
This page covers normal Shoppex event webhooks like order:paid and subscription:created. If you are implementing a DYNAMIC product with dynamic_webhook, use Dynamic Product Delivery.
You can register webhooks through the dashboard or manage webhook subscriptions through the Developer API. The event payload is the same either way.

Setting Up Webhooks

1

Create Endpoint

Create an HTTP endpoint on your server that accepts POST requests:
app.post('/webhooks/shoppex', (req, res) => {
  const event = req.body;
  // Handle event
  res.status(200).send('OK');
});
2

Register in Dashboard

Go to Settings → Webhooks → Add Endpoint and enter your URL.
3

Select Events

Choose explicit event names, for example:
  • order:paid
  • order:cancelled
  • subscription:created
If you use the Dev API, fetch the full allowlist from GET /dev/v1/webhooks/events.

Webhook Payload

All webhooks follow this structure:
{
  "event": "order:paid",
  "data": {
    "uniqid": "abc123def456",
    "type": "PRODUCT",
    "status": "COMPLETED",
    "gateway": "STRIPE",
    "total": 29.99,
    "total_display": 29.99,
    "currency": "USD",
    "exchange_rate": 1,
    "crypto_exchange_rate": 0,
    "crypto_gateway": null,
    "apm_method": "CARD",
    "customer_email": "[email protected]",
    // ... more event-specific fields
  },
  "created_at": 1705314650
}
Dashboard test deliveries use the same top-level envelope as live deliveries: event, data, and created_at. The values inside data are synthetic, but the signature covers the full raw JSON body exactly the same way as a real webhook.
Top-level webhook created_at is a Unix timestamp. Nested timestamps inside data can be ISO 8601 strings.
One invoice can produce more than one payment attempt over time. For example, a customer can retry a payment or switch gateways. Treat the Shoppex invoice ID and event type as the durable business signal, not a single provider session.
Order webhooks can also include payment-context fields like exchange_rate, crypto_exchange_rate, crypto_gateway, and apm_method. These help with reconciliation and analytics without requiring a follow-up invoice fetch in common cases.

Available Events

Common Order Events

EventDescription
order:paidOrder/invoice paid successfully
order:cancelledOrder cancelled or expired
order:paid:productOrder paid (includes full product data)
order:cancelled:productOrder cancelled (includes full product data)

Common Subscription Events

EventDescription
subscription:createdNew subscription started
subscription:cancelledSubscription cancelled
Shoppex supports more event names than the short guide tables above. Examples: order:created, order:updated, order:partial, order:disputed, subscription:trial:started, subscription:updated, subscription:renewed, subscription:upcoming, plus product/query/feedback/affiliate events. Use Webhook Events Reference or GET /dev/v1/webhooks/events for the full current list.

Verifying Webhooks

Always verify webhook signatures to ensure authenticity. Shoppex signs webhooks using HMAC-SHA512:
import crypto from 'crypto';

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac('sha512', secret)  // Note: SHA512, not SHA256
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post('/webhooks/shoppex', (req, res) => {
  const signature = req.headers['x-shoppex-signature'] as string;
  const isValid = verifyWebhook(req.rawBody, signature, WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook based on event type
  const { event, data } = req.body;

  switch (event) {
    case 'order:paid':
      // Handle successful payment
      break;
    case 'order:cancelled':
      // Handle cancellation
      break;
  }

  res.status(200).send('OK');
});
Shoppex signs the full JSON request body exactly as sent on the wire. Simple example: verify {"event":"order:paid","data":{...},"created_at":1775764170}, not just the inner data object. Use the per-webhook secret from Settings -> Webhooks for that endpoint. Do not verify JSON.stringify(req.body) after parsing. Verify the raw body bytes from the incoming HTTP request instead.

Webhook Headers

HeaderDescription
X-Shoppex-EventThe event type (e.g., order:paid)
X-Shoppex-SignatureHMAC-SHA512 signature
X-Shoppex-DeliveryUnique delivery ID for deduplication

Retry Policy

If your endpoint returns an error (non-2xx status), we retry:
DeliveryDelay
Initial attemptImmediate
Retry 12 minutes
Retry 24 minutes
Retry 38 minutes
Retry 416 minutes
After the 5th failed attempt, the webhook is marked as failed. You can manually retry from the dashboard.
Your endpoint must respond within 30 seconds or the request will timeout.

Best Practices

Process webhooks asynchronously. Return 200 immediately and handle the event in a background job.
Webhooks may be delivered more than once. Use the delivery ID to deduplicate, and make your fulfillment logic idempotent.
A provider can have retries, delayed confirmations, or multiple attempts for one invoice. Good example: mark an order fulfilled when Shoppex tells you the invoice is paid, not when you first see a provider-specific session ID.
Always verify webhook signatures in production to prevent spoofing. Simple example: use the raw request body, not a parsed and re-serialized object.
Dashboard test deliveries now use the same webhook envelope and core order fields as live deliveries. Simple example: a test order:paid webhook includes fields like gateway, currency, total_display, and apm_method, not just a minimal placeholder object.

Next Steps

Webhook Events Reference

Full list of all event types and payload schemas

Dynamic Delivery

Real-time product delivery via webhook response