Integration recipes

Copy/paste examples for common backends.

Recipes follow the same pattern: create an invoice with a WOW amount, send the customer to the hosted invoice page, then fulfill your order when the invoice is confirmed.

Conceptual flow

  1. Create an invoice via POST /api/core/invoices.
  2. Optional: include checkout_continue_url to show a customer-facing Continue button after confirmation.
  3. Redirect the customer to invoice_url.
  4. Wait for invoice.confirmed via webhook (recommended) or polling.

What to store

  • Your internal order id in metadata.order_id
  • The returned invoice.id

Environment variables

Keep secrets server-side. Never expose API keys or webhook secrets in browser code.

WOWCHECKOUT_API_KEY

Used to call authenticated endpoints.

WOWCHECKOUT_WEBHOOK_SECRET

Used to verify the X-Webhook-Secret header on webhook deliveries.

WOWCHECKOUT_API_BASE_URL

Public base URL, including /api/core: https://wowcheckout.such.software/api/core

BTCPay compatibility (WooCommerce)

Use the official WooCommerce Greenfield plugin with the compatibility endpoints. Keys remain view-only, and invoices stay non-custodial.

Setup steps

  1. Sign in to WOW Checkout to get your API key.
  2. Store id: returned by the login response or via GET /api/v1/stores. The stores call returns a single store (one per primary address); use the id field.
  3. Install the official BTCPay WooCommerce plugin.
  4. In WooCommerce → BTCPay settings, set:
    • Server URL: https://wowcheckout.such.software (the plugin appends /api/v1).
    • Store ID: the id returned from the stores call.
    • API key: your WOW Checkout API key.
    • Payment method: WOW-CHAIN (shows as WOW_CHAIN in WooCommerce).
    • Modal checkout is supported; the hosted invoice page is recommended for clarity.

Behavior notes

  • Authorization header: Authorization: token <api_key>.
  • Status mapping: pending → New, payment detected → Processing, confirmed → final.
  • Expired and Invalid statuses map directly.
  • Webhook deliveries use the BTCPay-Sig header.
# Verify compatibility endpoints
export WOWCHECKOUT_BTCPAY_URL="https://wowcheckout.such.software/api/v1"
export WOWCHECKOUT_API_KEY="wowcheckout_..."

curl -sS "$WOWCHECKOUT_BTCPAY_URL/stores" \
  -H "Authorization: token $WOWCHECKOUT_API_KEY"

curl

Create an invoice, then poll status until it is confirmed.

# 1) Create invoice
export WOWCHECKOUT_API_BASE_URL="https://wowcheckout.such.software/api/core"
export WOWCHECKOUT_API_KEY="wowcheckout_..."

curl -sS -X POST "$WOWCHECKOUT_API_BASE_URL/invoices" \
  -H "Authorization: ApiKey $WOWCHECKOUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_xmr": "0.15",
    "confirmation_target": 2,
    "metadata": { "order_id": "ORDER-1234" }
  }'
# 1b) Create invoice from fiat (non-binding conversion)
curl -sS -X POST "$WOWCHECKOUT_API_BASE_URL/invoices" \
  -H "Authorization: ApiKey $WOWCHECKOUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_fiat": "100.00",
    "currency": "USD",
    "confirmation_target": 2,
    "metadata": { "order_id": "ORDER-1234" }
  }'
# 2) Poll public status (no auth)
export INVOICE_ID="uuid-from-response"

curl -sS "$WOWCHECKOUT_API_BASE_URL/public/invoice/$INVOICE_ID"

Node.js

Example shows creating an invoice with fetch and receiving webhooks with Express.

// create-invoice.mjs
const API_BASE_URL = process.env.WOWCHECKOUT_API_BASE_URL;
const API_KEY = process.env.WOWCHECKOUT_API_KEY;

export async function createInvoice({ amountXmr, orderId }) {
  const response = await fetch(`${API_BASE_URL}/invoices`, {
    method: "POST",
    headers: {
      Authorization: `ApiKey ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount_xmr: String(amountXmr),
      confirmation_target: 10,
      metadata: { order_id: orderId },
    }),
  });

  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new Error(`wowcheckout create invoice failed: ${response.status} ${text}`);
  }

  return await response.json(); // includes invoice_url
}
// webhook-server.mjs
import express from "express";

const WEBHOOK_SECRET = process.env.WOWCHECKOUT_WEBHOOK_SECRET;

const app = express();
app.use(express.json({ type: "application/json" }));

app.post("/wowcheckout/webhook", (req, res) => {
  const headerSecret = req.get("x-webhook-secret") ?? "";
  if (!WEBHOOK_SECRET || headerSecret !== WEBHOOK_SECRET) {
    return res.sendStatus(401);
  }

  const event = req.body?.event;
  const invoice = req.body?.invoice;
  const orderId = invoice?.metadata?.order_id;

  if (event === "invoice.confirmed" && orderId) {
    // Mark your order paid here.
  }

  return res.sendStatus(204);
});

app.listen(3001, () => {
  console.log("Listening on http://localhost:3001/wowcheckout/webhook");
});

PHP

Minimal create-invoice example and a webhook receiver endpoint.

<?php
// create_invoice.php

$apiBaseUrl = getenv("WOWCHECKOUT_API_BASE_URL");
$apiKey = getenv("WOWCHECKOUT_API_KEY");

$payload = json_encode([
  "amount_xmr" => "0.15",
  "confirmation_target" => 10,
  "metadata" => ["order_id" => "ORDER-1234"],
]);

$ch = curl_init($apiBaseUrl . "/invoices");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  "Authorization: ApiKey " . $apiKey,
  "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false || $status < 200 || $status >= 300) {
  http_response_code(500);
  echo "wowcheckout error: HTTP " . $status;
  exit;
}

$invoice = json_decode($body, true);
echo $invoice["invoice_url"];
<?php
// webhook.php

$webhookSecret = getenv("WOWCHECKOUT_WEBHOOK_SECRET");
$headerSecret = $_SERVER["HTTP_X_WEBHOOK_SECRET"] ?? "";
if (!$webhookSecret || $headerSecret !== $webhookSecret) {
  http_response_code(401);
  exit;
}

$raw = file_get_contents("php://input");
$payload = json_decode($raw, true);
$event = $payload["event"] ?? null;
$invoice = $payload["invoice"] ?? null;
$orderId = $invoice["metadata"]["order_id"] ?? null;

if ($event === "invoice.confirmed" && $orderId) {
  // Mark your order paid here.
}

http_response_code(204);

Python

Create an invoice with requests and receive webhooks with FastAPI.

# create_invoice.py
import os
import requests

api_base_url = os.environ["WOWCHECKOUT_API_BASE_URL"].rstrip("/")
api_key = os.environ["WOWCHECKOUT_API_KEY"]

response = requests.post(
    f"{api_base_url}/invoices",
    headers={"Authorization": f"ApiKey {api_key}"},
    json={
        "amount_xmr": "0.15",
        "confirmation_target": 2,
        "metadata": {"order_id": "ORDER-1234"},
    },
    timeout=10,
)
response.raise_for_status()
invoice = response.json()
print(invoice["invoice_url"])
# webhook_server.py
import os
from fastapi import FastAPI, Header, HTTPException

app = FastAPI()
webhook_secret = os.environ["WOWCHECKOUT_WEBHOOK_SECRET"]

@app.post("/wowcheckout/webhook", status_code=204)
async def wowcheckout_webhook(payload: dict, x_webhook_secret: str | None = Header(default=None)):
    if not x_webhook_secret or x_webhook_secret != webhook_secret:
        raise HTTPException(status_code=401, detail="Invalid webhook secret")

    event = payload.get("event")
    invoice = payload.get("invoice") or {}
    metadata = invoice.get("metadata") or {}
    order_id = metadata.get("order_id")

    if event == "invoice.confirmed" and order_id:
        # Mark your order paid here.
        pass

    return None

Go

Create an invoice with net/http and receive webhooks with a standard handler.

// create_invoice.go
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "strings"
  "time"
)

type invoiceResponse struct {
  ID         string `json:"id"`
  InvoiceURL string `json:"invoice_url"`
}

func main() {
  apiBaseURL := strings.TrimRight(os.Getenv("WOWCHECKOUT_API_BASE_URL"), "/")
  apiKey := os.Getenv("WOWCHECKOUT_API_KEY")

  payload := map[string]any{
    "amount_xmr":          "0.15",
    "confirmation_target": 2,
    "metadata": map[string]any{
      "order_id": "ORDER-1234",
    },
  }

  body, _ := json.Marshal(payload)
  req, _ := http.NewRequest("POST", apiBaseURL+"/invoices", bytes.NewReader(body))
  req.Header.Set("Authorization", "ApiKey "+apiKey)
  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{Timeout: 10 * time.Second}
  resp, err := client.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
  if resp.StatusCode < 200 || resp.StatusCode >= 300 {
    data, _ := io.ReadAll(resp.Body)
    panic(fmt.Sprintf("wowcheckout create invoice failed: %d %s", resp.StatusCode, string(data)))
  }

  var invoice invoiceResponse
  if err := json.NewDecoder(resp.Body).Decode(&invoice); err != nil {
    panic(err)
  }
  fmt.Println(invoice.InvoiceURL)
}
// webhook_server.go
package main

import (
  "encoding/json"
  "net/http"
  "os"
)

type webhookPayload struct {
  Event   string `json:"event"`
  Invoice struct {
    Metadata map[string]any `json:"metadata"`
  } `json:"invoice"`
}

func main() {
  webhookSecret := os.Getenv("WOWCHECKOUT_WEBHOOK_SECRET")

  http.HandleFunc("/wowcheckout/webhook", func(w http.ResponseWriter, r *http.Request) {
    headerSecret := r.Header.Get("X-Webhook-Secret")
    if webhookSecret == "" || headerSecret != webhookSecret {
      w.WriteHeader(http.StatusUnauthorized)
      return
    }

    var payload webhookPayload
    if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    if payload.Event == "invoice.confirmed" {
      if orderID, ok := payload.Invoice.Metadata["order_id"].(string); ok && orderID != "" {
        // Mark your order paid here.
        _ = orderID
      }
    }

    w.WriteHeader(http.StatusNoContent)
  })

  _ = http.ListenAndServe(":3001", nil)
}