Getting Started

Welcome to the Payment Processor API documentation. This API allows you to accept cryptocurrency payments and manage payouts programmatically.

Base URL

https://api.shadowpay.world

Quick Start

To get started, you'll need:

  1. An API key for authentication (passed via the X-API-Key HTTP header)
  2. A configured wallet chain for your chosen blockchain network
  3. Choose a symbol and chain from the available currencies list
Recommended Flow: Merchants should use symbol + chain on write requests. The API resolves that to the canonical internal currency identifier for accounting and blockchain execution.

Example: Create Your First Payment

curl -X POST "https://api.shadowpay.world/post_create_order" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d "order_id=ORDER_001" \
  -d "amount=100" \
  -d "symbol=USDC" \
  -d "chain=sepolia" \
  -d "base_currency=EUR"

Response:

{
  "payment_url": "https://shadowpay.world/d4e5f6g7-h8i9-j0k1-l2m3-n4o5p6q7r8s9?token=4f2b3c..."
}

Authentication

All endpoints require your merchant API key passed via the X-API-Key HTTP header. The only exception is the hosted payment page, which uses the opaque token query parameter embedded in payment_url.

  • X-API-Key - Your secret API key passed as an HTTP header (format: sk_live_xxx or sk_test_xxx)
  • token - Opaque public payment access token automatically included in payment_url
Security Note: Never expose your API key in client-side code or public repositories. Your API key has full access to your merchant account.

Create Payment Order

POST /post_create_order

Creates a new cryptocurrency payment order and returns a payment URL for the customer.

Parameters

Parameter Type Required Description
order_id string ✓ Unique identifier for this order
amount string ✓ Payment amount in merchant's base currency (set during account creation, e.g., "100" for €100 or $100)
symbol string ✓ Merchant-facing asset symbol for the selected chain (for example USDC or USDT)
currency string Canonical internal asset identifier for compatibility or advanced integrations. Send either symbol or currency, but not both.
chain string ✓ Blockchain network (e.g., "ethereum", "sepolia", "base", "arbitrum")
base_currency string ✓ Base fiat currency for the amount (e.g., "EUR", "USD")

Example Request

curl -X POST "https://api.shadowpay.world/post_create_order" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "X-API-Key: sk_live_abc123xyz789" \
  -d "order_id=ORDER_12345" \
  -d "amount=100.50" \
  -d "symbol=USDC" \
  -d "chain=ethereum" \
  -d "base_currency=EUR"

Response

{
  "payment_url": "https://shadowpay.world/a1b2c3d4-e5f6-7890-abcd-ef1234567890?token=9a8b7c..."
}

Error Responses

  • 400 - Invalid amount format, invalid asset input, or cryptocurrency not available
  • 401 - Invalid authentication credentials
  • 404 - Cryptocurrency not found
  • 500 - No wallet chain configured or internal error
Recommended: Use symbol + chain for normal merchant integrations. Store the full payment_url exactly as returned, including its ?token=... query string.

Get Payment Info

GET /get_payment_infos/{payment_id}

Retrieves detailed information about a payment, including current status, amounts, and transaction events.

Access: Call this endpoint with either the token query parameter from payment_url or your merchant X-API-Key header.

Parameters

Parameter Type Description
payment_id UUID The unique payment identifier from the payment URL path
token string Opaque public access token from the payment_url query string. Optional only if you send X-API-Key.

Example Request

curl -X GET "https://api.shadowpay.world/get_payment_infos/a1b2c3d4-e5f6-7890-abcd-ef1234567890?token=9a8b7c..."

Response

{
  "crypto_price": 0.92,
  "amount": 100.0,
  "confirmed_amount": 50.0,
  "received_amount": 80.0,
  "expires_at": "2025-10-24T10:45:00.000Z",
  "updated_at": "2025-10-24T10:35:00.000Z",
  "qrcode": "data:image/png;base64,iVBORw0KGgo...",
  "chain": "sepolia",
  "status": "partially_received",
  "payment_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "currency": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
  "symbol": "USDC",
  "asset_type": "token",
  "base_currency": "EUR",
  "amount_base_currency": 92.0,
  "amount_base_paid": 46.0,
  "crypto_amount_paid": 50.0,
  "payment_uri": "ethereum:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb@11155111/transfer?address=0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238&uint256=100000000",
  "qr_format": "eip681",
  "transaction_events": [
    {
      "tx_hash": "0xabc123...",
      "amount": 50.0,
      "status": "confirmed",
      "block_number": 12340,
      "created_at": "2025-10-24T10:31:00.000Z",
      "updated_at": "2025-10-24T10:33:00.000Z"
    },
    {
      "tx_hash": "0xdef456...",
      "amount": 30.0,
      "status": "seen",
      "block_number": 12341,
      "created_at": "2025-10-24T10:35:00.000Z",
      "updated_at": "2025-10-24T10:35:00.000Z"
    }
  ]
}
Note: The QR code is generated using the payment URI stored with the order. For EVM tokens this is an EIP-681 transfer URI compatible with MetaMask, Rabby, Trust Wallet, and similar wallets.

Payment Status Values

The status field in the response can have one of the following values:

Status Description
created Payment order created
pending Payment is active and awaiting funds
partially_received Some crypto received on blockchain, but not full amount
received Full crypto amount received on blockchain (unconfirmed)
partially_confirmed Some amount confirmed on blockchain
confirmed Payment fully confirmed on blockchain (final state)
expired Payment window expired (default: 15 minutes)
failed Payment failed or cancelled

Get Payout Info

GET /get_payout_infos/{payout_id}

Retrieves detailed information about a payout/withdrawal, including current status, amounts, and transaction events.

Access: This endpoint requires the merchant X-API-Key header. Payout reads are not public.

Path Parameters

Parameter Type Description
payout_id UUID The unique payout identifier returned from create withdrawal

Example Request

curl -X GET "https://api.shadowpay.world/get_payout_infos/b1c2d3e4-f5g6-7890-hijk-lm1234567890" \
  -H "X-API-Key: sk_live_abc123xyz789"

Response

{
  "crypto_price": 0.92,
  "amount": 50.0,
  "status": "confirmed",
  "chain": "sepolia",
  "destination_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "currency": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
  "symbol": "USDC",
  "asset_type": "token",
  "base_currency": "EUR",
  "amount_base_currency": 46.0,
  "tx_hash": "0xabc123def456...",
  "created_at": "2025-10-24T11:00:00.000Z",
  "confirmed_at": "2025-10-24T11:05:00.000Z",
  "updated_at": "2025-10-24T11:05:00.000Z",
  "transaction_events": [
    {
      "tx_hash": "0xabc123def456...",
      "amount": 50.0,
      "status": "confirmed",
      "block_number": 12450,
      "created_at": "2025-10-24T11:02:00.000Z",
      "updated_at": "2025-10-24T11:05:00.000Z"
    }
  ]
}

Payout Status Values

The status field in the response can have one of the following values:

Status Description
pending Payout request created, waiting for processing
queued Payout queued for execution
sent Transaction sent to blockchain
confirmed Transaction confirmed on blockchain (final state)
failed Payout failed
cancelled Payout cancelled

List Available Currencies

POST /post_currencies

Returns a list of available cryptocurrencies with their current exchange rates and network information.

Headers

Parameter Type Required Description
X-API-Key string ✓ Your API key for authentication

Example Request

curl -X POST "https://api.shadowpay.world/post_currencies" \
  -H "X-API-Key: YOUR_API_KEY"

Response

[
  {
    "symbol": "USDC",
    "name": "USD Coin",
    "network": "ethereum",
    "rate": 1.00,
    "deposit_address": "0x1234567890abcdef1234567890abcdef12345678",
    "available": true
  },
  {
    "symbol": "USDC",
    "name": "USD Coin (Sepolia Testnet)",
    "network": "sepolia",
    "rate": 1.00,
    "deposit_address": "0x1234567890abcdef1234567890abcdef12345678",
    "available": true
  },
  {
    "symbol": "USDT",
    "name": "Tether",
    "network": "ethereum",
    "rate": 0.99,
    "deposit_address": "0x4567890123def4567890123def4567890123def",
    "available": true
  }
]
Note: The symbol field contains only the currency symbol (e.g., "USDC", "USDT") without the chain. Use the network field to identify which blockchain the currency is available on. The same currency can appear multiple times with different networks.
Write API requests: Use the symbol and network you choose here as the inputs to /post_create_order and /post_create_withdrawal. You do not need token addresses for normal merchant integrations.

Create Withdrawal

POST /post_create_withdrawal

Creates a withdrawal request to send cryptocurrency from your merchant wallet to a specified address.

Idempotency: Duplicate withdrawal requests with the same order_id will return the existing payout if it's still active.

Parameters

Parameter Type Required Description
order_id string ✓ Unique identifier for this withdrawal order
amount string ✓ Withdrawal amount in merchant's base currency (set during account creation, e.g., "100" for €100 or $100)
address_to string ✓ Destination blockchain address
chain string ✓ Blockchain network (ethereum, sepolia, arbitrum, base)
symbol string ✓ Merchant-facing asset symbol for the selected chain (for example USDC or USDT)
currency string Canonical internal asset identifier for compatibility or advanced integrations. Send either symbol or currency, but not both.
base_currency string ✓ Base fiat currency for the amount (e.g., "EUR", "USD")

Example Request

curl -X POST "https://api.shadowpay.world/post_create_withdrawal" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "X-API-Key: sk_live_abc123xyz789" \
  -d "order_id=WITHDRAWAL_001" \
  -d "amount=50" \
  -d "address_to=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
  -d "chain=sepolia" \
  -d "symbol=USDC" \
  -d "base_currency=EUR"

Response

{
  "payout_id": "b1c2d3e4-f5g6-7890-hijk-lm1234567890",
  "status": "queued",
  "payout_type": "withdrawal",
  "amount": 50.0,
  "symbol": "USDC",
  "chain": "sepolia",
  "source_wallet_address": "0x1234567890abcdef1234567890abcdef12345678",
  "destination_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "created_at": "2025-10-24T10:30:00.000Z"
}

Withdrawal Statuses

  • pending - Withdrawal row exists but has not yet been queued by the service
  • queued - Withdrawal accepted and queued for execution
  • sent - Transaction sent to blockchain
  • confirmed - Transaction confirmed on blockchain
  • failed - Withdrawal failed
  • cancelled - Withdrawal cancelled
Recommended: Use symbol + chain for merchant-facing integrations. Canonical currency remains available only for compatibility or advanced callers.

Webhooks

Webhooks allow you to receive real-time notifications when payment and payout events occur in your account. Instead of polling our API, webhooks push event data directly to your server.

Setup

To receive webhooks, you need to:

  1. Configure your webhook URL through the dashboard or internal admin tooling
  2. Store the generated webhook_secret securely
  3. Use that secret to verify incoming webhook signatures
Validation: Webhook URLs must use http or https, must not embed basic-auth credentials, and must not target localhost. Delivery also rejects destinations that resolve only to private or unroutable IPs unless they are in Tailscale or other trusted CIDRs configured by the operator.

Webhook Events

We currently send webhooks for the following events:

Event Type Description
payment.confirmed Payment has been fully confirmed on the blockchain
payout.confirmed Payout transaction successfully completed on the blockchain
payout.failed Payout transaction failed on the blockchain
Important: Webhooks are only sent for fully confirmed payments. Partial payments, expired payments, and failed payments do NOT trigger webhooks.

Webhook Payload Structure

Payment Webhook Example

{
  "event_type": "payment.confirmed",
  "event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created_at": "2024-12-03T10:30:45.123456+00:00",
  "payment": {
    "id": "d4e5f6g7-h8i9-j0k1-l2m3-n4o5p6q7r8s9",
    "merchant_order_id": "ORDER_12345",
    "crypto_amount": "100000000",
    "amount_base": "100.00",
    "crypto_currency": "USDC",
    "base_currency": "EUR",
    "status": "confirmed",
    "created_at": "2024-12-03T10:15:30.000000+00:00",
    "crypto_amount_paid": "100000000",
    "amount_base_paid": "100.00"
  }
}

Payout Webhook Example

{
  "event_type": "payout.confirmed",
  "event_id": "b2c3d4e5-f6g7-8901-bcde-f12345678901",
  "created_at": "2024-12-03T11:45:20.123456+00:00",
  "metadata": {
    "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "block_number": 12345678,
    "confirmed_at": "2024-12-03T11:45:15.000000+00:00"
  },
  "payout": {
    "id": "e5f6g7h8-i9j0-k1l2-m3n4-o5p6q7r8s9t0",
    "merchant_reference_id": "PAYOUT_456",
    "payout_type": "withdrawal",
    "crypto_amount": "50000000",
    "crypto_currency": "USDC",
    "amount_base": "50.00",
    "base_currency": "EUR",
    "status": "confirmed",
    "created_at": "2024-12-03T11:30:15.000000+00:00",
    "destination_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
  }
}

Webhook Security

All webhook requests include cryptographic signatures to verify their authenticity. This prevents spoofing and ensures the request genuinely comes from Shadow Pay.

Signature Headers

Each webhook request includes two security headers:

  • X-Webhook-Timestamp - Unix timestamp when the webhook was sent
  • X-Webhook-Signature - HMAC-SHA256 signature of the payload

Signature Verification Process

To verify a webhook is authentic:

  1. Extract X-Webhook-Timestamp and X-Webhook-Signature from headers
  2. Reconstruct the signed message: {timestamp}.{json_payload}
  3. Compute HMAC-SHA256 using your webhook secret: HMAC-SHA256(message, webhook_secret)
  4. Compare the computed signature with the received signature using a timing-safe comparison
# Signature verification logic
message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}"
expected_signature = hmac.new(webhook_secret, message.encode('utf-8'), hashlib.sha256).hexdigest()
is_valid = hmac.compare_digest(expected_signature, received_signature)

Retry Behavior

If your endpoint is unavailable or returns an error, we will automatically retry the webhook delivery:

  • Retry Schedule: 30s, 1m, 2m, 4m, 8m, 16m, 32m, 64m, 112.5m (total ~4 hours)
  • Maximum Attempts: 9 delivery attempts total
  • Success Codes: 200-299 HTTP status codes
  • Permanent Failures: 400, 401, 403, 404, 410, 422 (no retries)
  • Transient Failures: 5xx errors, timeouts, connection errors (will retry)
  • Callback Validation Failures: Invalid or blocked destinations fail permanently without retry
Best Practice: Your webhook endpoint should respond quickly (within 5 seconds) and return a 200 status code. Process the webhook asynchronously if needed.

Idempotency

Each webhook has a unique event_id. We guarantee at-least-once delivery, so you may receive duplicate webhooks. Use the event_id to make your webhook handling idempotent and prevent duplicate processing.

Testing Webhooks

During development:

  • Use tools like ngrok to expose your local server
  • Implement signature verification before deploying to production
  • Contact support to trigger test webhook events

Error Handling

The API uses standard HTTP response codes to indicate success or failure.

HTTP Status Codes

Code Description
200 Success - Request completed successfully
400 Bad Request - Invalid parameters or request format
401 Unauthorized - Invalid or missing authentication credentials
404 Not Found - Payment, order, or cryptocurrency not found
500 Internal Server Error - Something went wrong on the server

Error Response Format

{
  "detail": "Invalid authentication credentials"
}

Common Error Examples

Authentication Error (401)

{
  "detail": "Invalid authentication credentials"
}

Payment Not Found (404)

{
  "detail": "Payment a1b2c3d4-e5f6-7890-abcd-ef1234567890 not found"
}

Invalid Amount (400)

{
  "detail": "Invalid amount format"
}