Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.shoppex.io/llms.txt

Use this file to discover all available pages before exploring further.

Overview

There are three ways to accept payments with Shoppex:

Storefront

Customers browse your shop and checkout directly

Payment Links

Share a link that takes customers straight to checkout

API

Create invoices programmatically from your app
This guide covers both hosted/public Shoppex flows (storefront, payment links) and Developer API flows (POST /dev/v1/payments, POST /dev/v1/orders).

The Payment Flow

You don’t need to integrate with Stripe or PayPal directly. Shoppex handles all gateway communication for you.

Important Integration Rule

One Shoppex invoice can have more than one payment attempt over time. A customer might open checkout, try PayPal, abandon it, come back and finish with Stripe. This surprises most people integrating for the first time. The important thing: redirect the customer to the Shoppex checkout URL, listen for webhook events, and fulfill based on the final invoice status. Don’t build your fulfillment logic around a single provider-side session ID.

Method 1: Storefront (Hosted/Public)

Your storefront at yourshop.shoppex.io is ready out of the box:
  1. Customer browses products
  2. Adds to cart
  3. Completes checkout
  4. Receives product automatically
Best for: Digital products, subscriptions, simple e-commerce
Create a link that goes directly to checkout - perfect for sharing on social media, emails, or anywhere.

Create via Dashboard

  1. Go to Products → Your Product
  2. Click Copy Payment Link
  3. Share the URL: https://yourshop.shoppex.io/product/your-product

Create via API

const response = await fetch('https://api.shoppex.io/dev/v1/payments', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Pro License',
    email: 'customer@example.com',
    value: 49.99,
    currency: 'USD',
    webhook: 'https://your-site.com/api/shoppex/payment-webhook'
  })
});

const { data } = await response.json();
console.log(data.url); // https://checkout.shoppex.io/invoice/4ea04c92-5cc3-4ea8-845c-cd3c7085796c
console.log(data.webhook_secret); // returned once when webhook is provided
If you pass webhook, Shoppex sends payment lifecycle events only for that created payment/invoice to that URL. Store webhook_secret immediately; it is not returned on later reads. Best for: Social media sales, email campaigns, one-off payments

Method 3: API Integration (Developer API)

Use the Developer API when you need your own checkout UI or backend-driven payment flow.

Payment vs Order

POST /dev/v1/payments and POST /dev/v1/orders look similar, but they solve different problems. Use POST /dev/v1/payments when you have your own checkout and just want a hosted payment URL. You don’t get Shoppex catalog line items or automatic product delivery. Use POST /dev/v1/orders when you want to charge for real Shoppex products — with line items, variants, and automatic delivery (files, serials, subscriptions, or DYNAMIC). The quick rule: payments = generic developer payment. orders = catalog-backed Shoppex order.
Subscriptions, serials, files, and DYNAMIC delivery always require POST /dev/v1/orders.POST /dev/v1/payments creates a one-off developer invoice with a free-form title and amount. It does not link to catalog products, does not create subscription records, and does not appear in the Subscriptions dashboard tab.A common integration mistake: load a product from GET /dev/v1/products, then call POST /dev/v1/payments with that product’s title and price. That disconnects checkout from the catalog. Pass the product uniqid in POST /dev/v1/orders instead.The API returns 422 when POST /dev/v1/payments receives catalog fields such as product_id or items.Recurring-billing custom fields such as plan_type still create the payment, but the 201 response includes a warnings array with code subscription_checkout_mismatch pointing you to POST /dev/v1/orders.

Need to know which payment methods the shop currently accepts?

Use GET /dev/v1/me/capabilities for that. A common mistake: GET /dev/v1/payments returns existing payment records, not the shop’s payment configuration. If you want to know which gateways (Stripe, PayPal, Crypto) are enabled, use GET /dev/v1/me/capabilities instead.
White-label crypto checkout is available by manual approval. Contact support to request access.Use GET /dev/v1/me/capabilities to check whether white_label_crypto_checkout is enabled for your shop before you build a bot or custom crypto payment UI around it.

Skip the crypto picker on hosted checkout

If you want to keep Shoppex hosted checkout but open a concrete coin directly, pass:
  • gateway: the merchant crypto provider you want Shoppex to use
  • crypto_gateway: the concrete coin or network the buyer should pay with
This works for merchant crypto providers like OXAPAY and CRYPTOMUS.
cURL
curl -X POST https://api.shoppex.io/dev/v1/payments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Discord Boost Package",
    "email": "customer@example.com",
    "value": 59.99,
    "currency": "USD",
    "gateway": "OXAPAY",
    "crypto_gateway": "TRON",
    "white_label": false
  }'
Example response shape:
{
  "data": {
    "uniqid": "4ea04c92-5cc3-4ea8-845c-cd3c7085796c",
    "crypto_gateway": "TRON",
    "url": "https://checkout.shoppex.io/invoice/4ea04c92-5cc3-4ea8-845c-cd3c7085796c?selected_gateway=TRON&auto_start_gateway=1&auto_start_crypto=1",
    "url_branded": "https://checkout.shoppex.io/invoice/4ea04c92-5cc3-4ea8-845c-cd3c7085796c?selected_gateway=TRON&auto_start_gateway=1&auto_start_crypto=1&shop=12345"
  }
}
Simple example:
  • gateway: "OXAPAY" + crypto_gateway: "TRON" -> hosted checkout opens directly on TRON
  • gateway: "CRYPTOMUS" + crypto_gateway: "USDT_TRC20" -> hosted checkout opens directly on USDT TRC20
  • gateway: "NATIVE_CRYPTO" + crypto_gateway: "LITECOIN" -> Shoppex derives a non-custodial Litecoin receive address from your active native wallet
If you omit crypto_gateway, the buyer lands on the normal generic crypto payment selection first.

Native BTC/LTC Payments

Use this flow when you want BTC or LTC paid directly into your own non-custodial wallet. Shoppex derives a receive address from your active native wallet xpub, watches the chain, and marks the payment paid after the configured confirmations. Shoppex does not create a provider payment and does not hold keys or funds. Check availability first:
cURL
curl https://api.shoppex.io/dev/v1/me/capabilities \
  -H "Authorization: Bearer YOUR_API_KEY"
Look for:
{
  "data": {
    "features": {
      "nativeCryptoCheckout": {
        "available": true,
        "enabled": true,
        "gateways": ["BITCOIN", "LITECOIN"]
      }
    }
  }
}
Create a native Litecoin payment:
cURL
curl -X POST https://api.shoppex.io/dev/v1/payments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Discord Boost Package",
    "email": "customer@example.com",
    "value": 59.99,
    "currency": "USD",
    "gateway": "NATIVE_CRYPTO",
    "crypto_gateway": "LITECOIN",
    "white_label": false
  }'
Example response shape:
{
  "data": {
    "uniqid": "4ea04c92-5cc3-4ea8-845c-cd3c7085796c",
    "gateway": "NATIVE",
    "crypto_gateway": "LITECOIN",
    "crypto_address": "ltc1qexampleaddress",
    "crypto_amount": 0.42133742,
    "crypto_received": 0,
    "crypto_uri": "litecoin:ltc1qexampleaddress?amount=0.42133742",
    "crypto_confirmations_needed": 2,
    "status": "PENDING",
    "url": "https://checkout.shoppex.io/invoice/4ea04c92-5cc3-4ea8-845c-cd3c7085796c"
  }
}
If gateway is NATIVE_CRYPTO, Shoppex fails if no active native BTC/LTC wallet exists. If you only send crypto_gateway: "BITCOIN" or crypto_gateway: "LITECOIN", an active native wallet is prioritized.

White-label Crypto Checkout

White-label crypto checkout is deprecated for new integrations. Use Native BTC/LTC when you want Shoppex to derive non-custodial receive addresses from your own wallet. Use OxaPay or Cryptomus when you need an external crypto provider for other coins. Historical white-label crypto payments can still be read through the Developer API and can still complete through the normal payment lifecycle. New white-label payment creation may return 403 FORBIDDEN with:
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Shoppex-managed white-label crypto is deprecated. Use Native Crypto for BTC/LTC or connect OxaPay/Cryptomus."
  }
}

Create a Payment

const response = await fetch('https://api.shoppex.io/dev/v1/payments', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Order #123',
    email: 'customer@example.com',
    value: 29.99,
    currency: 'USD',
    // Optional settings
    gateway: 'STRIPE',           // Force specific gateway
    return_url: 'https://yoursite.com/success',
    cancel_url: 'https://yoursite.com/cancelled'
  })
});

const { data } = await response.json();

// Redirect customer to checkout
window.location.href = data.url;
If you create the payment with gateway: 'PANDABASE', Shoppex also returns checkout_url and session_id.
  • use data.checkout_url if you want the direct Pandabase checkout session URL
  • keep listening for Shoppex webhooks for the final paid/cancelled state
Server-side completion is also different:
  • POST /dev/v1/payments/:id/complete confirms a generic developer payment only
  • POST /dev/v1/orders/:id/fulfill completes and fulfills a catalog-backed order
  • POST /dev/v1/orders/:id/complete is the same completion pipeline, exposed as an alias for integrations that look for a complete endpoint

Handle the Webhook

After payment, Shoppex sends a webhook to your server. Use the webhook_secret returned by POST /dev/v1/payments for a per-payment webhook. Use the endpoint secret from Settings -> Webhooks only for global webhook endpoints.
import crypto from 'crypto';

function verifyWebhook(
  payload: string,
  signatureHeader: string,
  deliveryId: string,
  timestampHeader: string,
  secret: string,
): boolean {
  const parts = Object.fromEntries(signatureHeader.split(',').map((part) => {
    const [key, value] = part.trim().split('=');
    return [key, value ?? ''];
  }));
  if (!deliveryId || parts.v1 !== '' || parts.t !== timestampHeader || !parts.h) {
    return false;
  }
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t)) > 300) {
    return false;
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${deliveryId}.${parts.t}.${payload}`, 'utf8')
    .digest('hex');
  if (parts.h.length !== expected.length) return false;

  return crypto.timingSafeEqual(
    Buffer.from(parts.h, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

app.post('/webhooks/shoppex', async (req, res) => {
  // Verify signature (important!)
  const signature = req.headers['x-shoppex-signature-v2'] as string;
  const deliveryId = req.headers['x-shoppex-delivery'] as string;
  const timestamp = req.headers['x-shoppex-timestamp'] as string;
  if (!verifyWebhook(req.rawBody, signature, deliveryId, timestamp, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

  switch (event) {
    case 'order:paid':
      // Payment successful - fulfill the order
      await fulfillOrder(data.uniqid, data.customer_email);
      break;

    case 'order:cancelled':
      // Payment failed or expired
      await handleFailedPayment(data.uniqid);
      break;
  }

  res.status(200).send('OK');
});
Always verify webhook signatures in production. New integrations should use X-Shoppex-Signature-V2. See the Webhooks Guide for details.

Dynamic Product Fulfillment

If you use products with type: "DYNAMIC", there is one more callback contract to implement: dynamic_webhook. This is separate from normal Shoppex event webhooks and uses the product’s dynamic webhook signing secret, not the secret from Settings -> Webhooks. The order:paid webhook tells your app that the invoice is paid. The dynamic_webhook is a separate fulfillment callback that asks your server for the delivered token, key, or access data. Use the dedicated contract docs here:
FeatureInvoice (API)Payment Link
Use caseCustom checkout flowsQuick sharing
Customer emailRequiredOptional
Multiple productsYesSingle product
Custom fieldsYesProduct default
ExpirationConfigurableNo expiration
TrackingFull invoice dataBasic analytics
Rule of thumb:
  • Use Payment Links for simple, shareable checkouts
  • Use Invoices when you need control over the checkout or customer data

Payment Gateways

Configure your payment providers in Settings → Payments. Supports: Credit cards, Apple Pay, Google Pay, SEPA, Klarna, and more.
1

Get API Keys

Log in to Stripe DashboardDevelopers → API Keys
2

Add to Shoppex

Enter your Publishable Key and Secret Key in Settings → Payments → Stripe
3

Configure Webhook

In Stripe Dashboard, create a webhook pointing to:
https://api.shoppex.io/v1/webhooks/stripe/{your_shop_id}

PayPal

1

Create REST App

Go to PayPal Developer → Create App
2

Add Credentials

Enter Client ID and Secret in Settings → Payments → PayPal

Cryptocurrency

Two options available:
OptionSetupFees
Shoppex Wallet (BTC/LTC)Self-custodial wallet, created in-dashboardNetwork fees only
Cryptomus / OxapayAPI key onlyProvider fees

Testing Payments

Enable Test Mode in Settings before going live.

Test Card Numbers

CardNumberResult
Visa4242 4242 4242 4242Success
Mastercard5555 5555 5555 4444Success
Declined4000 0000 0000 0002Declined
3D Secure4000 0025 0000 3155Requires auth
Use any future expiry date and any 3-digit CVC.
These numbers are Stripe-specific. PayPal and crypto gateways have their own sandbox modes.

Test Checklist

1

Create Test Invoice

Create an invoice via dashboard or API
2

Complete Checkout

Pay with test card 4242 4242 4242 4242
3

Verify Webhook

Check that your webhook endpoint received order:paid
4

Check Fulfillment

Confirm the product was delivered (email, license, download)
Use ngrok to test webhooks locally:
ngrok http 3000
# Use the generated URL as your webhook endpoint

Common Scenarios

  1. Create product with File delivery type
  2. Upload your file
  3. Share your storefront or payment link
  4. Customer pays → receives download automatically
  1. Create product with Serials delivery type
  2. Add license keys (one per line)
  3. Customer pays → receives unique license key
  1. Create payment via API with customer email
  2. Redirect customer to data.url
  3. Listen for order:paid webhook
  4. Fulfill order in your system
  1. Create product with Subscription type
  2. Set billing interval (monthly, yearly, etc.)
  3. Customer pays → subscription created
  4. Renewals happen automatically
See Subscriptions Guide for details.

Next Steps

Invoices

Deep dive into invoice lifecycle and statuses

Webhooks

Set up real-time notifications

Subscriptions

Set up recurring billing

API Reference

Explore all endpoints