# Get current exchange rates from cache Source: https://docs.pawpayments.com/api-reference/catalog/get-current-exchange-rates-from-cache /api-reference/openapi.json get /api/v2/rates # List supported assets Source: https://docs.pawpayments.com/api-reference/catalog/list-supported-assets /api-reference/openapi.json get /api/v2/assets Returns every supported coin/token along with its chain, network family and the current global minimum invoice amount. # Create an invoice Source: https://docs.pawpayments.com/api-reference/invoices/create-an-invoice /api-reference/openapi.json post /api/v2/invoices Creates a new invoice. If `asset` is omitted, the customer picks the currency on the hosted checkout (the deposit address is allocated only after the choice is made). # Get an invoice by id Source: https://docs.pawpayments.com/api-reference/invoices/get-an-invoice-by-id /api-reference/openapi.json get /api/v2/invoices/{invoice_id} # List invoices Source: https://docs.pawpayments.com/api-reference/invoices/list-invoices /api-reference/openapi.json get /api/v2/invoices Returns a paginated, filterable list of invoices for the authenticated merchant. Supports filtering by status, asset, date range and a list of explicit ids via `order_ids`. # Resend the merchant webhook for this invoice Source: https://docs.pawpayments.com/api-reference/invoices/resend-the-merchant-webhook-for-this-invoice /api-reference/openapi.json post /api/v2/invoices/{invoice_id}/notify # Get the merchant's balance summary in USD Source: https://docs.pawpayments.com/api-reference/merchant/get-the-merchants-balance-summary-in-usd /api-reference/openapi.json get /api/v2/balance # Unified income/payout/refund feed Source: https://docs.pawpayments.com/api-reference/merchant/unified-incomepayoutrefund-feed /api-reference/openapi.json get /api/v2/ledger When `type` is omitted, all three streams are merged in time order. # List webhook delivery attempts Source: https://docs.pawpayments.com/api-reference/notifications/list-webhook-delivery-attempts /api-reference/openapi.json get /api/v2/notifications # Send a test webhook to either `url` from the body or the merchant's callback URL Source: https://docs.pawpayments.com/api-reference/notifications/send-a-test-webhook-to-either-`url`-from-the-body-or-the-merchants-callback-url /api-reference/openapi.json post /api/v2/notifications/test # Create a batch of up to 200 payouts. Per-item failures are reported individually Source: https://docs.pawpayments.com/api-reference/payouts/create-a-batch-of-up-to-200-payouts-per-item-failures-are-reported-individually /api-reference/openapi.json post /api/v2/payouts/batch # Create a payout Source: https://docs.pawpayments.com/api-reference/payouts/create-a-payout /api-reference/openapi.json post /api/v2/payouts Requires the request IP to be whitelisted for the merchant. The `x-uniq-id` header (UUIDv4) is mandatory and provides idempotency for 2 hours. # Get a payout by id Source: https://docs.pawpayments.com/api-reference/payouts/get-a-payout-by-id /api-reference/openapi.json get /api/v2/payouts/{payout_id} # List payouts Source: https://docs.pawpayments.com/api-reference/payouts/list-payouts /api-reference/openapi.json get /api/v2/payouts # Deactivate a permanent address Source: https://docs.pawpayments.com/api-reference/permanent-addresses/deactivate-a-permanent-address /api-reference/openapi.json delete /api/v2/permanent/{address_id} Marks the address inactive and unsubscribes it from on-chain notifications. # Get a permanent address by id Source: https://docs.pawpayments.com/api-reference/permanent-addresses/get-a-permanent-address-by-id /api-reference/openapi.json get /api/v2/permanent/{address_id} # Get-or-create a permanent deposit address Source: https://docs.pawpayments.com/api-reference/permanent-addresses/get-or-create-a-permanent-deposit-address /api-reference/openapi.json post /api/v2/permanent Returns the existing active address for `(user_id, family)` or creates a new one. One EVM address covers all EVM chains. # List permanent deposit addresses Source: https://docs.pawpayments.com/api-reference/permanent-addresses/list-permanent-deposit-addresses /api-reference/openapi.json get /api/v2/permanent # Simulate a payment on a sandbox invoice Source: https://docs.pawpayments.com/api-reference/sandbox/simulate-a-payment-on-a-sandbox-invoice /api-reference/openapi.json post /api/v2/invoices/{invoice_id}/simulate # Authentication Source: https://docs.pawpayments.com/authentication How to authenticate requests to the PawPayments API. Every `/api/v2/*` endpoint requires an API key. Pass it in the `x-api-key` header: ```bash theme={null} curl https://api.pawpayments.com/api/v2/balance \ -H "x-api-key: $PAW_API_KEY" ``` API keys are issued in the merchant dashboard. Each key is bound to a single merchant. ## Live and sandbox keys A merchant has two independent keys. The live key is issued once the domain is verified; the sandbox key is available immediately and is prefixed `sk_sandbox_`. The prefix is what selects the environment — the host and the paths are identical — so moving an integration between them is a one-line change. See [Sandbox](/sandbox). ## Payouts and IP allowlists Payout endpoints (`POST /api/v2/payouts`, `POST /api/v2/payouts/batch`) require the request IP to be in the merchant's `allowed_subnets`. Calls from any other IP receive `403 PAYOUTS_DISABLED`. Payout requests must also carry a fresh UUIDv4 in the `x-uniq-id` header. The same value is rejected for 2 hours, providing idempotency for retries and accidental double-submissions. ## Merchant status Requests are rejected with `403` when the merchant is in `paused`, `pending_verification` or `rejected` status, or when KYC is `pending` / `rejected`. Sandbox keys are exempt from the status and KYC gates — that is the point of the sandbox — except that a `rejected` merchant has no access to either environment. # Billing types Source: https://docs.pawpayments.com/billing-type How `billing_type` controls invoice settlement (STATIC vs VARY). Every invoice has a `billing_type` that decides what happens when the customer sends an amount different from the requested one and when the invoice is considered finalised. ## `STATIC` — service / product purchase Use this for fixed-price flows: subscription renewal, one-off product purchase, paid plan upgrade, etc. * The invoice expects exactly `amount`. * An **underpayment** moves the invoice to `partially_paid` **without** finalising it. The deposit address keeps listening, the customer can top up to the full amount, and only then does the invoice transition to `success`. If they never finish paying, the invoice expires (`expires_at`) and is moved to `cancelled`. The partial credit on the merchant balance is **kept** — `cancelled` reports the final under-paid state, it does not reverse the credit. `cancelled` is reachable **only** from this path: a `partially_paid` `STATIC` invoice that crossed `expires_at` without a top-up. A merchant webhook fires on this transition so the partial credit can be reconciled. * An **overpayment** transitions the invoice to `paid_over` (final). * An **exact payment** transitions the invoice to `success` (final). * The hosted checkout exposes the remaining amount via `initial_amount` / `initial_fiat_amount` so the page can render a "pay the rest" UI on a partial payment. Final statuses: `success`, `paid_over`, `failed`, `high_risk`, `cancelled`, `expired`. On TTL: `partially_paid` (merchant already credited the partial amount at AML time) → `cancelled`. Everything else still in `created` at TTL — including a sub-\$2 tiny-payment that AML never credited — → `expired`. ## `VARY` — balance top-up / open-ended deposit Use this when you don't know the exact amount in advance, e.g. a wallet top-up, a tip jar, or a [permanent address](#permanent-addresses). * Any incoming payment finalises the invoice in one shot: * covers `amount` → `success` * less than `amount` → `partially_paid` (treated as **finalized** — no further top-ups are accepted on the same invoice) * more than `amount` → `paid_over` * This makes `VARY` invoices a single-payment record of the deposit rather than an open-ended ledger. Final statuses: `success`, `partially_paid`, `paid_over`, `failed`, `high_risk`, `expired`. `VARY` does not produce `cancelled`: any deposit finalises the invoice in one shot, so on TTL only the no-deposit case remains (`created` → `expired`). ## Permanent addresses Setting `permanent_address: true` when creating an invoice binds it to the long-lived deposit address for `(merchant, user_id, asset family)`. The address is created on first use and reused for every subsequent invoice. Required when `permanent_address: true`: * `billing_type` **must be `VARY`**. * `user_id` must be supplied. * `asset` must belong to a family that supports permanent addresses (`evm`, `tron`, `solana`, `xrp`, `bitcoin`, `litecoin`, `bitcoincash`). TON and XMR are not supported for permanent addresses. When a deposit lands on a permanent address: 1. If there is a non-finalised invoice already attached to the address (e.g. a `created` invoice the merchant just created with `permanent_address: true`), the deposit is credited to **that invoice**. 2. Otherwise a new invoice is auto-created (`billing_type: VARY`) and credited. Either way the address keeps listening — finalised invoices on a permanent address never trigger an unsubscribe. # CoinPayments Source: https://docs.pawpayments.com/compat/coinpayments CoinPayments-compatible routes (legacy v1 api.php and v2 REST), authentication, IPN/webhooks, and unsupported features. Paw emulates both CoinPayments APIs. Point an existing CoinPayments integration at Paw by changing only the base URL. ## Base URLs | API | Use as | Paw URL | | --------- | --------------------- | ------------------------------------------------------------ | | Legacy v1 | Full URL to `api.php` | `https://api.pawpayments.com/compat/coinpayments/v1/api.php` | | v2 REST | SDK `base_url` | `https://api.pawpayments.com/compat/coinpayments` | The v2 SDK appends fixed paths (`/api/v2/merchant/invoices`, ...) to the base URL, so use exactly the base URL above — the request signature covers the full URL. ## Authentication The same Paw merchant `api_key` is used as every CoinPayments key (public key, private key, IPN secret, v2 client id and client secret). No separate credentials are configured. **Legacy v1** keeps the classic signing: | Field | Value | | ------------------ | ------------------------------------------ | | `key` (POST field) | Paw merchant `api_key` | | `HMAC` (header) | `HMAC-SHA512(raw_post_body, api_key)`, hex | **v2 REST** keeps the signed-header scheme: | Header | Value | | -------------------------- | ------------------------------------------------------------------------------ | | `X-CoinPayments-Client` | Paw merchant `api_key` | | `X-CoinPayments-Timestamp` | UTC `YYYY-MM-DDTHH:MM:SS` | | `X-CoinPayments-Signature` | `base64(HMAC-SHA256(api_key, BOM + METHOD + URL + CLIENT + TIMESTAMP + BODY))` | Payout commands require the caller IP address to be present in the merchant's payout IP whitelist. ## Supported **Legacy v1 (`POST .../api.php`, dispatched by `cmd`):** * Payments: `create_transaction`, `get_tx_info`, `get_tx_info_multi`, `get_callback_address` * Account: `rates`, `balances`, `get_basic_info` * Payouts: `create_withdrawal`, `create_mass_withdrawal` **v2 REST:** * Invoices: create (`POST /api/v2/merchant/invoices`), get, list, cancel * Buyer checkout: `GET /api/v1/invoices/{id}/payment-currencies/{currency}` (and `/status`) — anonymous * Catalogue: `GET /api/v1|v2/currencies`, `GET /api/v1/rates` — anonymous ## Currency codes * Legacy v1 uses dotted codes: `BTC`, `LTC`, `USDT.TRC20`, `USDC.BEP20`, `BNB.BSC`. * v2 uses numeric ids: fiat `USD = 5057`, `EUR = 5195`; crypto `BTC = 1`, `ETH = 4`, `TRX = 9`; tokens `id:contract` (e.g. `9:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` = USDT.TRC20). ## Not Supported These return a CoinPayments-shaped error (`{"error": "..."}` for v1): * Conversions: `convert_coins`, `get_conversion_limits` * Internal transfers / PayByName: `create_transfer`, `get_pbn_info` * Personal wallet addresses: `get_deposit_address` * **v2 payouts** (the wallet-centric `wallets/{id}/spend/*` flow). Use the legacy v1 `create_withdrawal` / `create_mass_withdrawal` for payouts. ## Idempotency * `create_mass_withdrawal`: each `wd[]` map key is treated as the idempotency key — re-sending the same key returns the existing payout, and reusing a key with different data is rejected. * `create_withdrawal`: the legacy single withdrawal has no client idempotency key, so Paw derives one from the request body. Identical retries (same address, amount, currency, callback, note) collapse onto one payout — which protects against double-submit and timeout retries — but two genuinely distinct withdrawals with identical fields would also collapse. Prefer `create_mass_withdrawal` with explicit keys when you need to send identical amounts. ## Webhooks / IPN Invoices created through `/compat/coinpayments/v1/...` send a **legacy IPN**: a form-encoded body with numeric `status` codes (`0` waiting, `1` pending, `100` complete, `-1` cancelled/timed-out) and an `HMAC` header (`HMAC-SHA512` over the raw body, keyed by `api_key`). Invoices created through `/compat/coinpayments/api/v2/...` send a **v2 webhook**: a JSON body `{ "id", "type", "timestamp", "invoice" }` with the `X-CoinPayments-Client`, `X-CoinPayments-Timestamp`, and `X-CoinPayments-Signature` headers. The signature uses the same HMAC-SHA256 scheme as API requests, computed over the delivery URL — so the CoinPayments SDK's `verifyWebhook(method, url, ...)` accepts it unchanged. # Cryptomus Source: https://docs.pawpayments.com/compat/cryptomus Cryptomus-compatible routes, authentication, webhooks, and unsupported features. Use this base URL in an existing Cryptomus integration: ```text theme={null} https://api.pawpayments.com/compat/cryptomus/v1 ``` Requests keep the Cryptomus authentication format: | Header | Value | | ---------- | ---------------------------------- | | `merchant` | Paw merchant ID as a string. | | `sign` | `md5(base64(json_body) + api_key)` | The same Paw `api_key` is used as both the Cryptomus payment key and payout key. Payout routes require the caller IP address to be present in the merchant's payout IP whitelist. ## Supported Supported categories: * Payments: create, info, list, resend, services, test webhook * Payouts: create, info, list, services, test webhook * Static wallet * Balance * Exchange rates Cryptomus `order_id` responses contain the Paw invoice or payout id. Sending that id again as `order_id` for the same merchant returns the existing invoice or payout. ## Not Supported These features are not available and return a provider-shaped `501 NOT_SUPPORTED` response: * `POST /v1/payment/mark-as-paid` * `POST /v1/payment/qr` * `/v1/recurrence/*` * `/v2/user-api/*` These request fields are accepted but ignored: * `course_source` * payout `priority` * payout `from_currency` * invoice `is_refresh` Code-based discounts are not supported. Use `discount_percent` when a simple invoice amount modifier is enough. ## Webhooks Cryptomus-compatible invoices send a JSON IPN body with a `sign` field in the body. The signature uses the same `md5(base64(json_body_without_sign) + api_key)` pattern expected by Cryptomus integrations. PHP-style slash escaping is enabled by default for signature generation, matching common Cryptomus and Heleket PHP examples. Disable the merchant setting `cryptomus_php_slashes` only if the merchant-side verifier expects unescaped slashes. For the full list of supported providers, see the [migration overview](./overview). # Heleket Source: https://docs.pawpayments.com/compat/heleket Heleket-compatible migration notes. Use this base URL in an existing Heleket integration: ```text theme={null} https://api.pawpayments.com/compat/heleket/v1 ``` Heleket is wire-compatible with Cryptomus for the merchant API: the same paths, headers, request signing, response envelopes, and webhook signing are used. For the supported categories and unsupported features, see [Cryptomus](./cryptomus). The only practical differences are the base URL and the invoice-level webhook format name stored by PawPayments. For the full list of supported providers, see the [migration overview](./overview). # NowPayments Source: https://docs.pawpayments.com/compat/nowpayments NowPayments-compatible routes, authentication, webhooks, and unsupported features. Use this base URL in an existing NowPayments integration: ```text theme={null} https://api.pawpayments.com/compat/nowpayments/v1 ``` Most requests keep the NowPayments `x-api-key` header and use the Paw merchant `api_key`. ## Payout Authentication NowPayments payouts normally require JWT authentication. Paw emulates that flow: * Call `POST /v1/auth`. * Send the Paw `api_key` as both `email` and `password`. * Use the returned JWT as `Authorization: Bearer ` for payout routes. The same payout routes also accept `x-api-key`. 2FA verification is accepted as a no-op because Paw does not require a separate 2FA step for compatibility payouts. Payout routes require the caller IP address to be present in the merchant's payout IP whitelist. ## Supported Supported categories: * Status * Currencies * Minimum amount * Estimate * Payments: create, get, list * Hosted invoices: create, get, invoice-payment * Mass payouts: create, get, list * Payout validate-address, fee, min-amount * Balance ## Not Supported These features are not available and return a NowPayments-shaped `501 NOT_SUPPORTED` response: * `/v1/conversion/*` * `/v1/subscriptions/*` * `/v1/subscription-plans/*` * `/v1/sub-partner/*` * `/v1/fiat-payout/*` * `/v1/recurring-payments/*` These request fields are accepted but ignored: * payment `fixed_rate` * payment `payout_address` * payment `payout_currency` * payment `payout_extra_id` ## Webhooks NowPayments-compatible invoices send an IPN body similar to the NowPayments payment-status response and include the `x-nowpayments-sig` header. The signature is `HMAC-SHA512` over the alphabetically sorted JSON body. Configure your merchant-side verifier to use the same Paw `api_key` as the IPN secret. For the full list of supported providers, see the [migration overview](./overview). # Migrate from another provider Source: https://docs.pawpayments.com/compat/overview Use PawPayments as a drop-in base URL for selected Cryptomus, Heleket, NowPayments, and CoinPayments integrations. The Compatibility API lets an existing merchant integration keep its provider SDK and request format while switching traffic to PawPayments. In most integrations, the merchant changes only the provider base URL: | Provider SDK | Paw base URL | | ------------------------ | ------------------------------------------------------------ | | Cryptomus | `https://api.pawpayments.com/compat/cryptomus/v1` | | Heleket | `https://api.pawpayments.com/compat/heleket/v1` | | NowPayments | `https://api.pawpayments.com/compat/nowpayments/v1` | | CoinPayments (legacy v1) | `https://api.pawpayments.com/compat/coinpayments/v1/api.php` | | CoinPayments (v2 REST) | `https://api.pawpayments.com/compat/coinpayments` | Use the same Paw merchant `api_key` for request signing. Cryptomus and Heleket SDKs send the Paw merchant ID as the `merchant` header and sign with `api_key`. NowPayments SDKs continue to send `x-api-key`. CoinPayments SDKs send the `api_key` as the `key`/client id and sign with it (HMAC-SHA512 for legacy, HMAC-SHA256 for v2). ## Where to change the base URL You only repoint the SDK (or your HTTP client) at the Paw compat base URL — paths, headers, request signing, and response parsing stay exactly the same. Below is the exact place to change it for each provider's official SDK. If you call the REST API directly without an SDK, just swap the scheme + host (keeping the version prefix) for the Paw base URL. ### Cryptomus / Heleket Default host: `https://api.cryptomus.com/` (Heleket: `https://api.heleket.com/`). The SDK appends `v1/payment`, `v1/payout`, etc. * **Official PHP SDK (`cryptomus/api-php-sdk`)**: edit the constant in `src/RequestBuilder.php`: ```php theme={null} // const API_URL = "https://api.cryptomus.com/"; const API_URL = "https://api.pawpayments.com/compat/cryptomus/"; // Heleket: .../compat/heleket/ ``` * **Other SDKs / raw REST**: set the API host so requests resolve to `https://api.pawpayments.com/compat/cryptomus/v1/...` (or `.../compat/heleket/v1/...`). Keep the `merchant` header (Paw merchant id) and `sign` (`md5(base64(json_body) + api_key)`) unchanged. ### NowPayments Default base: `https://api.nowpayments.io/v1/`. * **Official PHP SDK (`nowpayments-api-php`)**: change the class constant: ```php theme={null} // const API_BASE = 'https://api.nowpayments.io/v1/'; const API_BASE = 'https://api.pawpayments.com/compat/nowpayments/v1/'; ``` * **JavaScript / Go / other**: set the base-URL / host option to `https://api.pawpayments.com/compat/nowpayments/v1`. Keep sending `x-api-key`. The payout JWT flow (`POST /v1/auth`) works unchanged against the same base URL. ### CoinPayments — legacy v1 (`api.php`) Legacy SDKs post to a single full URL, `https://www.coinpayments.net/api.php`. * Set the SDK's API URL field/constant (e.g. `$this->url` / `API_URL` in the CoinPayments PHP class) to: ``` https://api.pawpayments.com/compat/coinpayments/v1/api.php ``` Keep the public `key` POST field and the `HMAC` header (HMAC-SHA512 of the raw body) unchanged. ### CoinPayments — v2 (REST) Default base: `https://a-api.coinpayments.net` (the SDK appends `/api/v2/merchant/...`). * Pass the base URL to the client constructor — **without** a trailing path: ```python theme={null} # Python (CoinPaymentsNet/sdk) client = CoinPaymentsClient(client_id, client_secret, base_url="https://api.pawpayments.com/compat/coinpayments") ``` PHP/JS equivalents set the same value (e.g. `ApiHelper::API_URL`). Use exactly `https://api.pawpayments.com/compat/coinpayments` (no extra path): the v2 signature covers the full request URL, so the base must match what Paw reconstructs. ## Webhooks Invoices created through a compatibility route automatically use that provider's webhook format: | Created through | Webhook format | | ----------------------------- | --------------------------------------------------------------------------- | | `/compat/cryptomus/v1` | Cryptomus IPN body with `sign` in JSON | | `/compat/heleket/v1` | Heleket-compatible IPN body with `sign` in JSON | | `/compat/nowpayments/v1` | NowPayments IPN body with `x-nowpayments-sig` header | | `/compat/coinpayments/v1` | CoinPayments IPN, form-encoded body + `HMAC` header (HMAC-SHA512) | | `/compat/coinpayments/api/v2` | CoinPayments v2 JSON body + `X-CoinPayments-Signature` header (HMAC-SHA256) | | Native API | Native format by default, or the invoice-level `webhook_format` override | Compatibility routes are documented here as a migration guide. For individual request field descriptions, keep using the original provider documentation. The sections below describe what Paw supports, what is ignored, and what returns `501 NOT_SUPPORTED`. # Errors Source: https://docs.pawpayments.com/errors Error envelope and common error codes. Failures return HTTP 4xx/5xx with a JSON body: ```json theme={null} { "ok": false, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [ { "field": "amount", "message": "Input should be greater than 0" } ] } } ``` Always branch on `error.code` rather than the human `message` — codes are stable, messages may change. ## Common codes | HTTP | `code` | Meaning | | ---- | ----------------------------------- | ----------------------------------------------------------------------------- | | 400 | `INVALID_ID` | The path id is not a valid ObjectId / UUID. | | 400 | `INVOICE_REJECTED` | Business rule violation while creating an invoice. | | 400 | `MISSING_HEADER` / `INVALID_HEADER` | Required header missing or malformed (e.g. `x-uniq-id`). | | 400 | `SANDBOX_UNSUPPORTED` | Endpoint is live-only; called with a sandbox key. See [Sandbox](/sandbox). | | 400 | `SANDBOX_ONLY` | Endpoint is sandbox-only (e.g. simulating a payment); called with a live key. | | 401 | `UNAUTHORIZED` | Missing or invalid `x-api-key`. | | 403 | `PAYOUTS_DISABLED` | IP not whitelisted for payouts. | | 404 | `NOT_FOUND` | The requested resource is invisible to this merchant. | | 409 | `DUPLICATE` | Same `x-uniq-id` already used. | | 422 | `VALIDATION_ERROR` | Body did not match the schema. `details` lists the failing fields. | # Overview Source: https://docs.pawpayments.com/overview Public REST API for the PawPayments crypto payment gateway. The PawPayments API lets merchants accept cryptocurrency payments across BTC, ETH, BSC, Polygon, Base, TRON, TON, Solana, XRP, LTC, BCH and Monero — both native coins and supported tokens (USDT, USDC, DAI and more). Invoices and payouts can be priced in any of 23 supported fiat currencies: USD, EUR, GBP, CAD, AUD, CHF, JPY, NZD, SGD, HKD, NGN, KRW, ILS, RON, ARS, INR, IDR, MXN, MYR, TRY, PLN, BRL, THB. USD is the internal accounting base — merchant balances and payout `fiat_amount_usd` are always denominated in USD, regardless of the invoice/payout `fiat_currency`. The API is organised around a few resources: * **Invoices** — one-shot payment requests with a hosted checkout page. Each invoice has a `billing_type` (`STATIC` for fixed-price service purchases, `VARY` for open-ended balance top-ups). Invoices can also be bound to a permanent address by passing `permanent_address: true` (requires `billing_type=VARY` and `user_id`). See the [Billing types](./billing-type) page for the full semantics. * **Permanent addresses** — long-lived per-user deposit addresses. * **Payouts** — outgoing transfers, single or in batches. * **Ledger / Notifications** — read-side endpoints for reconciliation and webhook delivery introspection. All endpoints live under `/api/v2/`. Responses follow a consistent envelope: ```json theme={null} { "ok": true, "result": { ... } } ``` or, on failure: ```json theme={null} { "ok": false, "error": { "code": "INVALID_ID", "message": "..." } } ``` List endpoints add a `pagination` object next to `result`. # BillManager Source: https://docs.pawpayments.com/plugins/billmanager PawPayments crypto checkout and top-up for BillManager 6.x. The BillManager integration adds: * a payment method (`PawPayments`) plugged into the standard BillManager payment flow, * a small CGI page for client‑initiated **top‑ups** that credit the client account on payment. Ready-to-install `pawpayments-billmgr-.zip` (includes `install.sh`). Source, releases, and issue tracker. | Property | Value | | --------------- | -------------------------------------------------------------------------------------- | | GitHub | [`pawpayments/billmgr-plugin`](https://github.com/pawpayments/billmgr-plugin) | | Download | [Latest release `.zip`](https://github.com/pawpayments/billmgr-plugin/releases/latest) | | BillManager | 6.x (tested on 5.437+) | | PHP | 7.4 (BillManager bundles 7.4 by default; 8.x also works) | | Required access | `root` on the BillManager host (installer writes into `/usr/local/mgr5/`) | | HTTPS | Required — Paw delivers webhooks over TLS only | ## File layout After installation the files live under `/usr/local/mgr5/`: ``` etc/xml/billmgr_mod_pmpawpayments.xml ← plugin manifest + form paymethods/pmpawpayments ← config CLI handler (executable) cgi/pawpaymentspayment.php ← checkout: create invoice + redirect cgi/pawpaymentsresult.php ← checkout webhook receiver cgi/pawpaymentstopup.php ← top-up CGI form cgi/pawpaymentstopup_result.php ← top-up webhook receiver include/php/pawpayments_util.php ← helpers (LocalQuery, logging) include/php/vendor/pawpayments/sdk/... ← vendored PHP SDK skins/... ← icon assets (optional) ``` ## Install The release ships an `install.sh` that copies everything into place and reloads the BillManager `core` worker. ```bash theme={null} cp -r billmgr-plugin/ /root/ touch /root/billmgr-plugin/.pawpayments # marker file required by the installer cd /root/billmgr-plugin chmod +x install.sh ./install.sh ``` To install manually: ```bash theme={null} cp -r etc paymethods cgi include skins /usr/local/mgr5/ chmod 755 /usr/local/mgr5/paymethods/pmpawpayments chmod 755 /usr/local/mgr5/cgi/pawpayments*.php killall core # gracefully reloads the BillManager core ``` The new payment method appears as **PawPayments** under **Settings → Payment methods**. ## Configure the payment method Use the BillManager UI: **Settings → Payment methods → Add → Module: PawPayments** and fill the wizard. | Field | Description | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | Visible name (e.g. *PawPayments Crypto*). | | Currency | The fiat currency you bill in. PawPayments accepts USD, EUR, GBP, CAD, AUD, CHF, JPY, NZD, SGD, HKD, NGN, KRW, ILS, RON, ARS, INR, IDR, MXN, MYR, TRY, PLN, BRL, THB. Unsupported BillManager currencies are rejected with `VALIDATION_ERROR` when the invoice is created. | | Project | The BillManager project this method is available in. | | API Key | Merchant API key from the Paw dashboard. | | API Base URL | `https://api.pawpayments.com` (or leave empty to use the default). | | Invoice TTL (seconds) | Default `3600`. | For headless deployments you can also seed the row directly: ```sql theme={null} SET @next_id := (SELECT COALESCE(MAX(id), 0) + 1 FROM paymethod); SET @xmlp := 'YOUR_API_KEYhttps://api.pawpayments.com3600'; INSERT INTO paymethod (id, orderpriority, name, active, currency, module, xmlparams, recurring, allowrefund, profiletype) VALUES (@next_id, 10, 'PawPayments Crypto', 'on', (SELECT id FROM currency WHERE iso = 'USD' LIMIT 1), 'pmpawpayments', @xmlp, 'off', 'off', ''); ``` You will still need to attach the new `paymethod` to your project (`paymethod2project`) and to a group (`paymethod2group`) for it to appear in the customer area. ## Webhook URLs | Purpose | URL | | -------- | ---------------------------------------------------------- | | Checkout | `https:///mancgi/pawpaymentsresult.php` | | Top‑up | `https:///mancgi/pawpaymentstopup_result.php` | Both endpoints must be reachable over HTTPS. If you front BillManager with Nginx, make sure `/mancgi/` is proxied to the BillManager `ihttpd` (port `1500`): ```nginx theme={null} location / { proxy_pass https://:1500; proxy_ssl_verify off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ``` ## Lifecycle **Checkout.** The CGI builds a Paw invoice via `POST /api/v2/invoices`, redirects to `payment_url`, and on webhook success calls `payment.setpaid` exactly once — payment status is checked first, so retries never re‑mark a payment as paid. **Top‑up.** The customer enters an amount on `pawpaymentstopup.php`; the CGI creates a Paw invoice and stores its `order_id`. The webhook handler verifies the signature, checks `externalid` for duplicates, and creates a BillManager `payment` row already marked as paid — BillManager itself converts that row into account credit on the next billing cycle event. Webhooks carrying a `permanent_address_id` are silently acknowledged with `200 OK`. ## Smoke test ```bash theme={null} curl -X POST "https:///mancgi/pawpaymentsresult.php" \ -H "Content-Type: application/json" \ -d '{}' \ -w "\nHTTP %{http_code}\n" ``` Expected: `Missing data` with HTTP 200 — confirms the script loads and parses JSON. End‑to‑end check with a valid signature: ```bash theme={null} KEY="" ORDER_ID="" ELID="" BODY="{\"order_id\":\"$ORDER_ID\",\"extra\":\"$ELID\",\"status\":\"success\",\"fiat_amount\":\"25\",\"asset\":\"USDT\"}" SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$KEY" | awk '{print $2}') curl -X POST "https:///mancgi/pawpaymentsresult.php" \ -H "Content-Type: application/json" \ -H "X-Paw-Signature: $SIG" \ -d "$BODY" ``` Expected: `OK` with HTTP 200. ## Troubleshooting | Symptom | Cause / Fix | | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **PawPayments** does not appear in **Add payment method** | XML manifest not loaded. Run `killall core` (BillManager auto‑restarts the worker) or `service ihttpd restart`. Check that `/usr/local/mgr5/etc/xml/billmgr_mod_pmpawpayments.xml` exists. | | `mgrctl paymethod.add ... ERROR missed(currency)` | The `mgrctl` wizard is stateful — pass parameters in the right step or use the UI / direct DB seed. | | Webhook returns `Payment not found` | The BillManager payment row referenced by `extra` does not exist, or it has no project / profile. Configure projects, profiles, and accounts before testing. | | Webhook returns `Invalid signature` | The API key on the payment method does not match the one used to issue the invoice. Update **Settings → Payment methods → PawPayments → API Key** and re‑issue. | | Top‑up not crediting funds | The CGI creates a payment row marked as paid; BillManager itself converts it to credit on the next billing cycle event. Check `tail -f /usr/local/mgr5/var/billmgr.log`. | | Nginx returns `502 Bad Gateway` for `/mancgi/...` | `ihttpd` is not listening on the address Nginx is proxying to. Confirm `ss -tlnp \| grep 1500` and update `proxy_pass`. | Plugin debug messages are written to BillManager's main log: ```bash theme={null} tail -f /usr/local/mgr5/var/billmgr.log | grep -i pawpayments ``` ## Uninstall ```bash theme={null} rm -f /usr/local/mgr5/etc/xml/billmgr_mod_pmpawpayments.xml rm -f /usr/local/mgr5/paymethods/pmpawpayments rm -f /usr/local/mgr5/cgi/pawpayments*.php rm -rf /usr/local/mgr5/include/php/vendor/pawpayments rm -f /usr/local/mgr5/include/php/pawpayments_util.php rm -rf /usr/local/mgr5/skins/orion/images/pawpayments mysql billmgr -e "DELETE FROM paymethod WHERE module='pmpawpayments';" killall core ``` Existing payment history is preserved — BillManager keeps `payment` rows even after the method is removed. # Blesta Source: https://docs.pawpayments.com/plugins/blesta Crypto checkout for Blesta via the official PawPayments non-merchant gateway. The Blesta integration ships as a single **non-merchant payment gateway** (`PawPayments (Crypto)`) that pays any Blesta invoice with cryptocurrency. Customers are redirected to the hosted PawPayments paywall to pick an asset and network; the invoice is reconciled automatically once the on-chain payment confirms and PawPayments delivers a signed webhook to Blesta's gateway callback URL. Ready-to-upload `pawpayments-blesta-.zip`. Source, releases, and issue tracker. | Property | Value | | ------------------- | ------------------------------------------------------------------------------------- | | GitHub | [`pawpayments/blesta-plugin`](https://github.com/pawpayments/blesta-plugin) | | Download | [Latest release `.zip`](https://github.com/pawpayments/blesta-plugin/releases/latest) | | Blesta | 4.x / 5.x | | PHP | 7.4+ (8.1+ recommended) | | Required extensions | `curl`, `json`, `openssl` | | Database | Whatever Blesta already uses (MySQL / MariaDB) | ## Install Download `pawpayments-blesta-.zip` from the [GitHub Releases page](https://github.com/pawpayments/blesta-plugin/releases/latest) and unzip it. The archive mirrors the Blesta install tree — one non-merchant gateway: ``` components/ └── gateways/ └── nonmerchant/ └── pawpayments/ ├── pawpayments.php ← gateway class ├── config.json ← metadata + supported fiats ├── init.php ← loads the vendored SDK ├── language/en_us/… ← language strings ├── views/default/ ← settings + redirect templates └── vendor/pawpayments/sdk/ ← vendored PHP SDK ``` Drop the `pawpayments` folder into your Blesta root at `components/gateways/nonmerchant/`, then restore ownership: ```bash theme={null} chown -R www-data:www-data components/gateways/nonmerchant/pawpayments ``` ## Activate the gateway 1. **Settings → Payment Gateways → Available**. 2. Click **Install** next to **PawPayments (Crypto)**. 3. Fill the fields: | Field | Value | | --------------------- | ------------------------------------------------------------------- | | API Key | Merchant API key from the Paw dashboard (stored encrypted). | | API Base URL | `https://api.pawpayments.com` (default). | | Invoice TTL (seconds) | Lifetime of the underlying Paw invoice. Default `3600` (300–86400). | 4. Under **Settings → Company → Currencies**, enable the currencies you invoice in — the gateway supports the fiats listed in `config.json`. ## Webhook URL PawPayments sends `notify_url` on every invoice it creates, so manual webhook setup is not required. Blesta routes the callback to the gateway's `validate()` method at: ``` https:///callback/gw/{company_id}/pawpayments/ ``` The settings page prints the exact URL for your company. It must be reachable over **HTTPS** from the public internet — PawPayments requires `notify_url` to use the `https` scheme and refuses to deliver to private, loopback, or link-local hosts. ## Lifecycle | Step | Method | What happens | | --------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Client clicks **Pay** | `buildProcess()` | Creates a Paw invoice (`POST /api/v2/invoices`, `billing_type=STATIC` — a fixed-price order stays open after an underpayment so the client can top it up) and redirects to `payment_url`. The Blesta invoice IDs + client ID ride along in `metadata`; the client ID is also set as `extra`. | | Payment confirms | `validate()` | Paw POSTs a signed webhook to the callback URL. The raw body is verified against `X-Paw-Signature`, the status is mapped, and the transaction is applied to the originating invoices, keyed by the Paw `order_id` (`transaction_id`). | | Client returns | `success()` | The browser lands on Blesta's return URL with the order ID + invoice data appended, reconciling to the same `transaction_id` as the webhook. | Because the Paw `order_id` is used as the Blesta `transaction_id`, duplicate webhook deliveries and the browser return never double-pay an invoice. Webhooks carrying a `permanent_address_id` (not bound to a Blesta invoice) are silently acknowledged. ### Status mapping | PawPayments status | Blesta status | | ---------------------------------- | --------------------------------- | | `success`, `paid_over` | `approved` | | `confirming`, `partially_paid` | `pending` | | `failed`, `cancelled`, `high_risk` | `declined` | | anything else | ignored (no transaction recorded) | ## Smoke test (checkout) The webhook body carries the Blesta invoice list in `metadata.invoices` (`id=amount` pairs joined by `|`) and the client ID in `metadata.client_id`: ```bash theme={null} COMPANY_ID="1" ORDER_ID="" KEY="" BODY="{\"order_id\":\"$ORDER_ID\",\"status\":\"success\",\"fiat_amount\":2.00,\"fiat_currency\":\"USD\",\"extra\":\"42\",\"metadata\":{\"source\":\"blesta\",\"flow\":\"checkout\",\"client_id\":\"42\",\"invoices\":\"101=2.00\"}}" SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$KEY" | awk '{print $2}') curl -X POST "https:///callback/gw/$COMPANY_ID/pawpayments/" \ -H "Content-Type: application/json" \ -H "X-Paw-Signature: $SIG" \ -d "$BODY" ``` Expected: HTTP 200, and invoice `101` transitions toward **Paid** once the transaction is applied. A wrong or missing signature returns HTTP 401. You can also replay the real webhook for an existing invoice with `POST /api/v2/invoices/{order_id}/notify`. ## Troubleshooting | Symptom | Cause / Fix | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Gateway not under **Settings → Payment Gateways → Available** | Files not uploaded or wrong path. Verify `components/gateways/nonmerchant/pawpayments/pawpayments.php` exists and is readable by the web user. | | `The payment could not be created` on checkout | API key wrong / merchant not activated, or the currency is not enabled for the company. Check **Gateway → Logs**. | | Webhook returns HTTP 401 | The API key in gateway settings does not match the one used to issue the invoice, or the body was altered in transit. | | Invoice created but never marked paid | `notify_url` not reachable over HTTPS, or the callback host is private / loopback. PawPayments refuses such hosts — expose Blesta on a public HTTPS domain. | Gateway activity is logged under **Settings → Company → Gateway Logs** (or the per-gateway **Logs** tab). ## Uninstall Uninstall the gateway from **Settings → Payment Gateways → Installed**, then remove the files: ```bash theme={null} cd /path/to/blesta rm -rf components/gateways/nonmerchant/pawpayments ``` # Node.js SDK Source: https://docs.pawpayments.com/plugins/node-sdk Official TypeScript-first Node.js client for the PawPayments Native V2 API — built-in fetch, ESM and CJS, webhook helpers. The `@pawpayments/sdk` package is the official Node.js SDK for the Native V2 API. It covers the same REST resources as the Python SDK, uses only built-in `fetch` at runtime (no HTTP dependencies), ships ES modules and CommonJS builds plus `.d.ts`, and exposes webhook verification helpers aligned with other official integrations. The published npm package. Source, releases, and issue tracker. | Property | Value | | -------------------- | -------------------------------------------------------------------- | | Package name | `@pawpayments/sdk` | | npm | [`@pawpayments/sdk`](https://www.npmjs.com/package/@pawpayments/sdk) | | GitHub | [`pawpayments/node-sdk`](https://github.com/pawpayments/node-sdk) | | Minimum Node.js | 18 | | Runtime dependencies | None (`fetch` required) | ## Installation ```bash theme={null} npm install @pawpayments/sdk ``` ```bash theme={null} pnpm add @pawpayments/sdk ``` ```bash theme={null} yarn add @pawpayments/sdk ``` ## Quick start ```ts theme={null} import { PawPayments } from "@pawpayments/sdk"; const paw = new PawPayments({ apiKey: process.env.PAW_API_KEY! }); const invoice = await paw.invoices.create({ amount: 25, fiat_currency: "USD", billing_type: "STATIC", asset: "usdt_tron", description: "Pro plan, 1 month", notify_url: "https://example.com/paw/webhook", }); console.log(invoice.payment_url); ``` `PawPaymentsOptions` also accepts `baseUrl` (default `https://api.pawpayments.com`), `timeoutMs`, and a custom `fetch` implementation for constrained runtimes. ## Resources Successful calls return unwrapped `result` payloads (or list envelopes where the API paginates). Failures throw `PawPaymentsApiError` with `code`, `httpStatus`, `message`, and optional `details`. | Attribute | Methods | | ------------------- | ----------------------------------------------------------------------------- | | `paw.assets` | `list()` | | `paw.rates` | `get({ base, assets })` | | `paw.balance` | `get()` | | `paw.invoices` | `create()`, `get(id)`, `list()`, `notify(id)` | | `paw.payouts` | `create(params, { uniqId })`, `get(id)`, `list()`, `batch(items, { uniqId })` | | `paw.ledger` | `list({ ... })` | | `paw.notifications` | `list()`, `test(url?)` | | `paw.permanent` | `create()`, `get(id)`, `list()`, `deactivate(id)` | `payouts.create` and `payouts.batch` accept optional `uniqId` for idempotency (UUIDv4). If omitted, the SDK uses `crypto.randomUUID()`; duplicates within two hours yield HTTP `409`. ## Webhook verification Validate against the **raw body** buffer or `Uint8Array` your framework exposes — parsing JSON first and re-stringifying will change bytes and fail HMAC checks. ```ts theme={null} import { Webhook } from "@pawpayments/sdk"; const sigHeader = req.header("X-Paw-Signature") ?? ""; if (!Webhook.verifyRawBody(req.body, sigHeader, process.env.PAW_API_KEY!)) { return res.status(401).end(); } const payload = Webhook.parsePayload(req.body); ``` Permanent-address callbacks that include `permanent_address_id` should return `200 OK` without duplicating checkout-specific side effects, consistent with the PHP plugins. ## Errors ```ts theme={null} import { PawPaymentsApiError } from "@pawpayments/sdk"; try { await paw.invoices.create({}); } catch (err) { if (err instanceof PawPaymentsApiError) { console.error(err.code, err.httpStatus, err.message, err.details); } } ``` # Plugins & SDKs Source: https://docs.pawpayments.com/plugins/overview Official PawPayments plugins plus PHP, Python, and Node.js SDKs for the Native V2 API. PawPayments ships first-party SDKs for PHP, Python, and Node.js that talk to the documented `/api/v2/*` surface, plus plugins for popular billing and e‑commerce platforms. The PHP SDK underpins WooCommerce, WHMCS, Blesta, and BillManager; Python and Node clients mirror the full Native V2 API (invoices, payouts, ledger, permanent addresses, and more). Plugins use only public endpoints — no hidden backend logic — so anything they do is reproducible from your own integration. | Integration | Platform | Flows | Source | Get it | | ---------------------------- | -------------------------- | ---------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [PHP SDK](./php-sdk) | Composer package | Create / fetch invoices, verify webhooks | [GitHub](https://github.com/pawpayments/php-sdk) | [Packagist](https://packagist.org/packages/pawpayments/sdk) | | [Python SDK](./python-sdk) | PyPI (`pawpayments`) | Full Native V2 API + webhooks | [GitHub](https://github.com/pawpayments/python-sdk) | [PyPI](https://pypi.org/project/pawpayments/) | | [Node.js SDK](./node-sdk) | npm (`@pawpayments/sdk`) | Full Native V2 API + webhooks | [GitHub](https://github.com/pawpayments/node-sdk) | [npm](https://www.npmjs.com/package/@pawpayments/sdk) | | [WooCommerce](./woocommerce) | WordPress + WooCommerce 7+ | Checkout | [GitHub](https://github.com/pawpayments/woocommerce-plugin) | [Download .zip](https://github.com/pawpayments/woocommerce-plugin/releases/latest) | | [WHMCS](./whmcs) | WHMCS 8.x | Checkout, Add Funds (top‑up) | [GitHub](https://github.com/pawpayments/whmcs-plugin) | [Download .zip](https://github.com/pawpayments/whmcs-plugin/releases/latest) | | [Blesta](./blesta) | Blesta 4.x / 5.x | Checkout | [GitHub](https://github.com/pawpayments/blesta-plugin) | [Download .zip](https://github.com/pawpayments/blesta-plugin/releases/latest) | | [BillManager](./billmanager) | BillManager 6.x | Checkout, top‑up | [GitHub](https://github.com/pawpayments/billmgr-plugin) | [Download .zip](https://github.com/pawpayments/billmgr-plugin/releases/latest) | ## Common conventions All plugins are intentionally thin wrappers around the public API and follow the same conventions, so they can coexist on a single merchant account without colliding: * `extra` carries the platform invoice / order ID for checkout flows, and the client / account ID for top‑up flows. * `metadata.source` is set to `whmcs`, `woocommerce`, `blesta`, or `billmanager`. * `metadata.flow` is `checkout` or `topup`. * Checkout and top‑up always use **separate `notify_url` endpoints**, so a single PawPayments callback URL setting on the merchant side is not required. * Currency and network selection happens entirely on the PawPayments paywall — the plugins never need to know which assets are enabled for the merchant. ## Webhook handling Every plugin verifies the `X-Paw-Signature` header (HMAC‑SHA256 of the raw body, keyed with the merchant's API key). Webhooks that carry a `permanent_address_id` field — i.e. deposits to long‑lived addresses, not to a one‑shot invoice — are silently acknowledged with `200 OK` and never produce duplicate orders or credits. Permanent‑address flows belong to your own backend integration, not to a checkout plugin. ## Idempotency Each plugin protects against double‑credit on webhook retries: * **WooCommerce** — `payment_complete()` is natively idempotent; the order can only transition out of `on-hold` once. * **WHMCS** — checkout uses `addInvoicePayment`'s `transactionId` deduplication; top‑up uses a small `mod_pawpayments_credits` table keyed by `order_id`. * **BillManager** — checkout reads payment status before calling `payment.setpaid`; top‑up checks `externalid` before issuing a new payment. # PHP SDK Source: https://docs.pawpayments.com/plugins/php-sdk Lightweight PHP client for the PawPayments API v2 and webhook signature verification. `pawpayments/sdk` is a tiny dependency‑free PHP library used by all official plugins. You can also install it directly to integrate PawPayments into a custom PHP application. The published Composer package. Source, releases, and issue tracker. | Property | Value | | -------------------- | ------------------------------------------------------------------- | | Package name | `pawpayments/sdk` | | Packagist | [`pawpayments/sdk`](https://packagist.org/packages/pawpayments/sdk) | | GitHub | [`pawpayments/php-sdk`](https://github.com/pawpayments/php-sdk) | | Minimum PHP | 7.4 (8.x supported) | | Required extensions | `ext-curl`, `ext-json` | | Runtime dependencies | None | ## Installation ```bash theme={null} composer require pawpayments/sdk ``` For environments without Composer (legacy WHMCS / BillManager hosts) the SDK can also be vendored manually — every plugin in this repository ships an embedded copy under its own `vendor/` directory. ## Quick start ```php theme={null} use PawPayments\Sdk\PawPaymentsClient; use PawPayments\Sdk\Exception\PawPaymentsApiException; $client = new PawPaymentsClient( apiKey: getenv('PAW_API_KEY'), baseUrl: 'https://api.pawpayments.com', // default ); try { $invoice = $client->createInvoice([ 'fiat' => 'USD', 'fiat_amount' => '25.00', 'billing_type' => 'STATIC', 'extra' => 'order-1042', 'notify_url' => 'https://example.com/webhooks/paw', 'metadata' => ['source' => 'custom-app', 'flow' => 'checkout'], ]); header('Location: ' . $invoice['payment_url']); } catch (PawPaymentsApiException $e) { error_log('Paw API error: ' . $e->getCode() . ' ' . $e->getMessage()); } ``` The client always returns the unwrapped `result` object from the standard `{ ok, result }` response envelope, and throws `PawPaymentsApiException` (with the upstream error `code`, `message`, and HTTP status) for both transport and API failures. ## Methods | Method | Endpoint | Notes | | ------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------- | | `createInvoice(array $params): array` | `POST /api/v2/invoices` | Returns the created invoice including `payment_url`, `order_id`, `external_id`. | | `getInvoice(string $orderId): array` | `GET /api/v2/invoices/{order_id}` | Used for status polling and idempotent checks. | The SDK is intentionally minimal — anything not covered by these two methods is one `curl` call away. The same authentication header (`x-api-key: `) and the same response envelope are used on every endpoint. ## Webhook verification `PawPayments\Sdk\Webhook::verify()` validates the HMAC‑SHA256 signature delivered in the `X-Paw-Signature` header. Use the **raw request body** — JSON‑re‑encoding the payload before hashing will break the comparison. ```php theme={null} use PawPayments\Sdk\Webhook; $raw = file_get_contents('php://input'); $sigHdr = $_SERVER['HTTP_X_PAW_SIGNATURE'] ?? ''; $payload = json_decode($raw, true) ?: []; if (!Webhook::verify($raw, $sigHdr, $payload, $apiKey)) { http_response_code(401); echo 'Invalid signature'; return; } if (!empty($payload['permanent_address_id'])) { http_response_code(200); echo 'OK'; return; } // Process $payload['order_id'], $payload['status'], $payload['fiat_amount']… ``` # Python SDK Source: https://docs.pawpayments.com/plugins/python-sdk Official Python client for the PawPayments Native V2 API — sync and async clients plus webhook verification. The `pawpayments` package is the official Python SDK for the Native V2 API. It exposes resource helpers that mirror the public REST surface (`/api/v2/*`), ships both a synchronous client (`PawPayments`, `requests`) and an asynchronous one (`AsyncPawPayments`, `httpx`), and includes helpers for verifying webhook signatures. The published PyPI package. Source, releases, and issue tracker. | Property | Value | | -------------------- | --------------------------------------------------------------------- | | Package name | `pawpayments` | | PyPI | [`pawpayments`](https://pypi.org/project/pawpayments/) | | GitHub | [`pawpayments/python-sdk`](https://github.com/pawpayments/python-sdk) | | Minimum Python | 3.9 | | Runtime dependencies | `requests`, `httpx` | ## Installation ```bash theme={null} pip install pawpayments ``` ## Quick start (sync) ```python theme={null} from pawpayments import PawPayments paw = PawPayments(api_key="…") invoice = paw.invoices.create( amount=25, fiat_currency="USD", billing_type="STATIC", asset="usdt_tron", description="Pro plan, 1 month", notify_url="https://example.com/paw/webhook", ) print(invoice["payment_url"]) ``` Optional constructor arguments match other official clients: `base_url` defaults to `https://api.pawpayments.com`, `timeout` defaults to `30` seconds, and you may inject a shared `requests.Session`. ## Quick start (async) ```python theme={null} import asyncio from pawpayments import AsyncPawPayments async def main(): async with AsyncPawPayments(api_key="…") as paw: invoice = await paw.invoices.create( amount=25, fiat_currency="USD", billing_type="STATIC", asset="usdt_tron", ) print(invoice["payment_url"]) asyncio.run(main()) ``` ## Resources Successful responses unwrap the standard `{ ok, result }` envelope into plain dicts (or typed list wrappers where applicable). API and transport failures raise `PawPaymentsApiError` with `code`, `http_status`, and optional `details`. | Attribute | Methods | | ------------------- | --------------------------------------------------------------------------------- | | `paw.assets` | `list()` | | `paw.rates` | `get(base=..., assets=...)` | | `paw.balance` | `get()` | | `paw.invoices` | `create(**...)`, `get(order_id)`, `list(**...)`, `notify(order_id)` | | `paw.payouts` | `create(..., uniq_id=...)`, `get(id)`, `list(**...)`, `batch(items, uniq_id=...)` | | `paw.ledger` | `list(**...)` | | `paw.notifications` | `list(**...)`, `test(url=...)` | | `paw.permanent` | `create(**...)`, `get(id)`, `list(**...)`, `deactivate(id)` | For `payouts.create` and `payouts.batch`, pass `uniq_id` (UUIDv4) for explicit idempotency. If omitted, the SDK generates one; repeating the same id within two hours returns HTTP `409`. ## Webhook verification Use the **raw request body** bytes when validating `X-Paw-Signature` (HMAC-SHA256 with your API key). Re-encoding JSON before hashing breaks verification. ```python theme={null} from flask import abort from pawpayments import Webhook raw = request.get_data() sig = request.headers.get("X-Paw-Signature", "") if not Webhook.verify_raw_body(raw, sig, api_key): abort(401) payload = Webhook.parse_payload(raw) ``` Treat webhooks that include `permanent_address_id` like the official plugins: acknowledge with `200 OK` without treating them as checkout callbacks unless your integration owns permanent-address flows. ## Errors ```python theme={null} from pawpayments import PawPaymentsApiError try: paw.invoices.create(...) except PawPaymentsApiError as exc: print(exc.code, exc.http_status, str(exc), exc.details) ``` # WHMCS Source: https://docs.pawpayments.com/plugins/whmcs Crypto checkout and Add Funds top-up for WHMCS via the official PawPayments gateway and addon. The WHMCS integration ships as **two** components that share a single API key: * a **payment gateway** (`PawPayments (Crypto)`) that pays any WHMCS invoice with crypto, * an optional **addon** (`PawPayments Crypto Deposit`) that adds an *Add Funds* page where customers credit their WHMCS balance with crypto. Ready-to-upload `pawpayments-whmcs-.zip` (gateway + addon). Source, releases, and issue tracker. | Property | Value | | ------------------- | ------------------------------------------------------------------------------------ | | GitHub | [`pawpayments/whmcs-plugin`](https://github.com/pawpayments/whmcs-plugin) | | Download | [Latest release `.zip`](https://github.com/pawpayments/whmcs-plugin/releases/latest) | | WHMCS | 8.x | | PHP | 7.4+ (8.1+ recommended; WHMCS 8.x supports up to 8.3) | | Required extensions | `curl`, `json`, `mbstring`, `openssl` | | Database | Whatever WHMCS already uses (MySQL / MariaDB) | ## File layout The release zip mirrors the WHMCS install tree: ``` modules/ ├── gateways/ │ ├── pawpayments.php ← gateway entry point │ ├── pawpayments/ ← vendored SDK │ │ └── vendor/pawpayments/sdk/... │ └── callback/ │ ├── pawpayments.php ← checkout webhook │ └── pawpayments_topup.php ← top-up webhook └── addons/ └── pawpayments_topup/ └── pawpayments_topup.php ← top-up addon (Add Funds) ``` Drop those folders directly into your WHMCS root, then restore ownership: ```bash theme={null} chown -R www-data:www-data \ modules/gateways/pawpayments.php \ modules/gateways/pawpayments \ modules/gateways/callback/pawpayments*.php \ modules/addons/pawpayments_topup ``` ## Activate the gateway 1. **Setup → Payments → Payment Gateways → All Payment Gateways**. 2. Click **PawPayments (Crypto)** to activate. 3. Fill the fields: | Field | Value | | --------------------- | ------------------------------------------------------------------------------------------------------------ | | Display Name | Customer‑facing label, e.g. *PawPayments (Crypto)*. | | API Key | Merchant API key from the Paw dashboard. | | API Base URL | `https://api.pawpayments.com` (default). | | Invoice TTL (seconds) | Lifetime of the underlying Paw invoice. Default `3600`. The same `payment_url` is reused inside that window. | ## Activate the Add Funds addon (optional) 1. **Setup → Addon Modules → PawPayments Crypto Deposit → Activate**. 2. **Configure** — leave the **API Key** field empty to inherit the gateway's key, or enter a separate one. 3. Grant access to the desired admin roles, save. The addon creates `mod_pawpayments_credits` on activation; this small table provides idempotency for the top‑up webhook (primary key on `order_id`). The customer top‑up page is then available at `https:///index.php?m=pawpayments_topup`. Link to it from a custom client‑area menu item. ## Webhook URLs The plugin sends `notify_url` on every invoice it creates, so manual webhook setup is not required. The endpoints are: | Purpose | URL | | -------- | ---------------------------------------------------------------------- | | Checkout | `https:///modules/gateways/callback/pawpayments.php` | | Top‑up | `https:///modules/gateways/callback/pawpayments_topup.php` | If the merchant account requires a *default* webhook URL, use the checkout one. Both endpoints must be reachable over HTTPS. ## Lifecycle **Checkout.** `addInvoicePayment` is called with the Paw `order_id` as `transactionId`, so duplicate webhook deliveries never double‑pay an invoice. **Top‑up.** Each top‑up creates a one‑shot Paw invoice via `POST /api/v2/invoices` and is recorded in `mod_pawpayments_credits` keyed by `order_id`. On webhook success the WHMCS `AddCredit` API is called, visible on **Clients → Client Profile → Summary → Credit Balance**. Webhooks carrying a `permanent_address_id` are silently acknowledged with `200 OK`. ## Smoke test (checkout) ```bash theme={null} ORDER_ID="" INVOICE_ID="" KEY="" BODY="{\"order_id\":\"$ORDER_ID\",\"extra\":\"$INVOICE_ID\",\"status\":\"success\",\"fiat_amount\":\"15\",\"asset\":\"USDT\"}" SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$KEY" | awk '{print $2}') curl -X POST "https:///modules/gateways/callback/pawpayments.php" \ -H "Content-Type: application/json" \ -H "X-Paw-Signature: $SIG" \ -d "$BODY" ``` Expected: `OK` with HTTP 200, and the WHMCS invoice transitions to **Paid**. ## Troubleshooting | Symptom | Cause / Fix | | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Gateway not in the list at **Setup → Payments → Payment Gateways** | Files not uploaded or wrong path. Verify `modules/gateways/pawpayments.php` exists and is readable by the web user. | | `Failed to create payment: Invalid API key` | API key wrong or merchant not activated. | | Webhook returns HTTP 500 with `Failed to open required init.php` | Outdated callback. Current callback uses `require_once __DIR__ . '/../../../init.php';` (three `..`, not four). Re‑upload the latest `callback/pawpayments*.php`. | | Webhook returns HTTP 401 *Invalid signature* | The API key in gateway settings does not match the one used to issue the invoice. Update settings and re‑issue. | | Top‑up addon not visible | Activate it under **Setup → Addon Modules** and grant access to your admin role. | | `Module Not Activated` on top‑up | The gateway must be enabled — the addon falls back to the gateway's API key. | | Duplicate top‑ups | `mod_pawpayments_credits` prevents this. If the table is missing, re‑activate the addon. | WHMCS gateway logs are at **Utilities → Logs → Gateway Log** (filter by `pawpayments`). ## Uninstall ```bash theme={null} cd /path/to/whmcs rm -rf modules/gateways/pawpayments.php \ modules/gateways/pawpayments \ modules/gateways/callback/pawpayments.php \ modules/gateways/callback/pawpayments_topup.php \ modules/addons/pawpayments_topup ``` Deactivate the addon and gateway from the WHMCS admin first. Optionally drop the bookkeeping table: ```sql theme={null} DROP TABLE IF EXISTS mod_pawpayments_credits; ``` # WooCommerce Source: https://docs.pawpayments.com/plugins/woocommerce Accept crypto payments in WooCommerce via the official PawPayments gateway plugin. The WooCommerce plugin adds a single payment gateway — *PawPayments (Crypto)* — to the WooCommerce checkout. It is checkout‑only: WooCommerce has no native credit balance, so there is no top‑up flow. Ready-to-upload `pawpayments-for-woocommerce-.zip`. Source, releases, and issue tracker. | Property | Value | | ------------------- | ------------------------------------------------------------------------------------------ | | GitHub | [`pawpayments/woocommerce-plugin`](https://github.com/pawpayments/woocommerce-plugin) | | Download | [Latest release `.zip`](https://github.com/pawpayments/woocommerce-plugin/releases/latest) | | WordPress | 6.0+ | | WooCommerce | 7.0+ | | PHP | 7.4+ (8.0+ recommended) | | Required extensions | `ext-curl`, `ext-json` | | HPOS | Compatible (High‑Performance Order Storage) | ## Install 1. Download the latest `pawpayments-for-woocommerce-.zip` from the [GitHub Releases page](https://github.com/pawpayments/woocommerce-plugin/releases/latest) (or build it yourself with `zip -r ... woocommerce-plugin`). 2. **Plugins → Add New → Upload Plugin**, choose the zip, click **Install Now**, then **Activate**. 3. **WooCommerce → Settings → Payments**, find **PawPayments (Crypto)**, click **Manage**. ## Configure | Field | Value | | ---------------- | -------------------------------------------------------- | | Enable / Disable | Tick to expose the gateway at checkout. | | Title | Customer‑facing label (e.g. *Pay with Crypto*). | | Description | Optional helper text under the title. | | API Key | Merchant API key from the Paw dashboard. | | API Base URL | `https://api.pawpayments.com` (default). | | Debug Log | Writes to **WooCommerce → Status → Logs → pawpayments**. | ## Webhook WooCommerce auto‑registers the handler at `https:///?wc-api=pawpayments`. The plugin sends this URL as `notify_url` on every invoice it creates, so you do **not** need to set a callback URL on the merchant side. ``` https:///?wc-api=pawpayments (always works) https:///wc-api/pawpayments (with pretty permalinks) ``` ## Order lifecycle 1. Customer chooses *Pay with Crypto* and is redirected to a `https://paw.now/invoice#…` paywall. 2. The order is created in **On hold** with the invoice id stored in `_pawpayments_order_id`. 3. After the on‑chain payment confirms, Paw posts a webhook with `status=success`. The plugin verifies the `X-Paw-Signature` header and calls `payment_complete()` — the order becomes **Processing** (or **Completed** for virtual goods) with a `PawPayments: Paid X USDT (order …)` note. `payment_complete()` is natively idempotent, so duplicate webhook deliveries never double‑credit an order. Webhooks carrying a `permanent_address_id` are silently acknowledged with `200 OK`. ## Smoke test ```bash theme={null} ORDER_ID="" EXTRA="" KEY="" BODY="{\"order_id\":\"$ORDER_ID\",\"extra\":\"$EXTRA\",\"status\":\"success\",\"amount\":\"10\",\"asset\":\"USDT\"}" SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$KEY" | awk '{print $2}') curl -X POST "https:///?wc-api=pawpayments" \ -H "Content-Type: application/json" \ -H "X-Paw-Signature: $SIG" \ -d "$BODY" ``` A healthy response is `OK` with HTTP 200, and the order transitions to *Processing*. ## Troubleshooting | Symptom | Likely cause | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Gateway not visible at checkout | Disabled in settings, or cart currency not enabled on the merchant account. | | `Payment error: Invalid API key` | Wrong key, or the merchant account is not yet activated. | | Order stays *On hold* after payment | Webhook not delivered — confirm the store is reachable over HTTPS and not behind a firewall blocking outbound Paw deliveries. | | Webhook returns HTTP 401 | Signature mismatch — the gateway's API key differs from the one used when the invoice was issued. | | No log entries | Enable **Debug Log** and check **WooCommerce → Status → Logs**. | ## Uninstall Deactivate and delete the plugin via **Plugins → Installed Plugins**. Settings live in `wp_options.woocommerce_pawpayments_settings`; remove with `wp option delete woocommerce_pawpayments_settings` if needed. The plugin does not create any custom database tables. # Webhooks Source: https://docs.pawpayments.com/webhooks Outgoing webhook format, delivery semantics, and signature verification. When an invoice changes status, PawPayments POSTs an invoice snapshot (JSON) to the merchant's `notify_url` (per-invoice override) or to the merchant-level `callback_url`. Delivery is **automatic** and **idempotent** — every merchant receives at most one webhook per terminal transition of each invoice. ## Delivery guarantees * **Automatic.** Fires on every status change of an invoice created via `POST /api/v2/invoices` (and on auto-created invoices bound to permanent addresses). * **Persistent retries.** Delivery attempts run as a durable background job, so they survive process restarts. On 2xx → done. On 4xx (except `408`/`425`/`429`) → give up immediately (merchant is presumed misconfigured). Otherwise the job is retried with exponential backoff: **30s, 1m, 2m, 5m, 10m, 20m, 40m, 80m, 160m** — up to **10 attempts** total (≈5h wall-clock). * **URL safety.** Each attempt DNS-resolves the callback and refuses to deliver if the host is private, loopback, link-local, or unresolvable. These refusals are logged as attempts too (with a human-readable reason). * **Source IPs.** All webhooks originate from the subnet `94.249.204.0/24`. Allowlist this range if your endpoint is firewalled or restricted to known senders. * **Audit log.** Every delivery attempt is recorded: URL, status code, response body (first 500 chars), attempt number, and timestamp. Inspect via `GET /api/v2/notifications` or replay a specific invoice on demand via `POST /api/v2/invoices/{id}/notify`. ## Headers | Header | Purpose | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `Content-Type: application/json` | Body is a single JSON object. | | `User-Agent: Mozilla/5.0 (compatible; PawWebhook/2.0; ...)` | Identifies the sender. | | `X-Paw-Signature` | HMAC-SHA256 of the **exact raw body** keyed with the merchant's API key (hex-encoded). | | `X-Paw-Sandbox: true` | Present only on sandbox deliveries. | ## Payload The payload is the same invoice shape returned by `GET /api/v2/invoices/{id}` (see the API Reference for the canonical schema). Authenticity is established exclusively by the `X-Paw-Signature` header — there is no `signature` field inside the JSON body. Key fields for merchant processing: | Field | Meaning | | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `order_id` | Unique 24-char hex id of the invoice. | | `external_id` | Public checkout id (UUID) used in the hosted-checkout URL. | | `status` | One of `confirming`, `partially_paid`, `paid_over`, `success`, `failed`, `high_risk`, `cancelled`. `cancelled` fires only when a `partially_paid` + `billing_type=STATIC` invoice crosses `expires_at` without a top-up — the partial credit on the merchant balance is **kept** and the webhook lets you reconcile the final under-paid state. Webhooks are NOT delivered for `expired` (TTL passed with no successful credit — `created`/`waiting` rows, including sub-\$2 tiny-payment deferrals that AML never enqueued) or `refunded` (admin-driven refund) — those state changes are visible via `GET /api/v2/invoices/{id}` but never push. | | `billing_type` | `STATIC` or `VARY` — see [Billing types](./billing-type). | | `asset` / `type` | Crypto asset (`usdt_tron`, `eth_eth`, …) and `native`/`token`. | | `amount` / `initial_amount` | Requested crypto amount (immutable). | | `fiat_amount` / `initial_fiat_amount` | What the deposit was worth when it was credited (live rate, net of commission) / the amount the invoice was issued for. On a settled invoice `fiat_amount` can land a fraction under `initial_fiat_amount` — settle against `initial_fiat_amount`, or the larger of the two on `paid_over`. | | `address_to` / `address_from` | Deposit address and (when known) sender. | | `received_amount` / `txid` | Last on-chain payment details. | | `expires_at` | Unix timestamp when the invoice stops accepting deposits. | | `processed_at` | Unix timestamp of the terminal transition. | | `metadata` | Arbitrary key/value dict passed on invoice creation. | A finalised webhook (`success`, `paid_over`, `failed`, `high_risk`, `cancelled`) carries the authoritative payment state — treat it as the source of truth and disregard any earlier `confirming` / `partially_paid` deliveries for the same invoice. ## Verification (Node.js) ```ts theme={null} import crypto from "node:crypto"; function verify(rawBody: Buffer, signatureHeader: string, apiKey: string) { const expected = crypto .createHmac("sha256", apiKey) .update(rawBody) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader), ); } ``` **Important:** verify against the **raw request body** before JSON-parsing. Any whitespace / key-reordering will produce a different signature. ## Verification (PHP) ```php theme={null} function verify(string $rawBody, string $signatureHeader, string $apiKey): bool { $expected = hash_hmac('sha256', $rawBody, $apiKey); return hash_equals($expected, $signatureHeader); } ``` ## Replay and introspection * `POST /api/v2/invoices/{id}/notify` — re-fires the webhook for the invoice on demand (useful for recovery or local-dev testing). * `GET /api/v2/notifications` — paginated audit log of every delivery attempt (successful, retrying, or given up). ## Sandbox webhooks Sandbox deliveries are identical in shape, with two markers: the payload carries `"sandbox": true` and the request carries `X-Paw-Sandbox: true`. The signature is keyed with the **sandbox** API key, so a receiver verifying against the live key will reject a sandbox webhook — verify with whichever key that environment is configured with. See [Sandbox](/sandbox) for driving invoices through their lifecycle by hand.