Merchant
Create a payment and redirect the customer.
Create a payment with a single POST to https://stixpay.com/api/payment/ (note the trailing /) and redirect your customer using the returned redirect_url (add merchant_id for reseller mode).
Create a payment and redirect the customer.
Same API with merchant_id.
Merchant webhooks: payment.succeeded, payment.failed, transaction_payout_success, plus KYB, refunds, payouts, and more — see Webhooks.
Create a payment for your merchant account. Send the request with your merchant API key and redirect the customer to the returned redirect_url.
Trailing slash: include the final / in the base URL. Calling the path without it may trigger an HTTP redirect; some clients then repeat the request as GET and lose your JSON body.
| Content-Type | application/json |
| X-API-Key | Your merchant API key |
| Field | Example | Expected value |
|---|---|---|
| amount | 47.25 | Positive decimal amount in major currency units (not cents). |
| currency | USD | ISO 4217 currency code (e.g. USD, EUR, GBP). |
| payment_method | credit_card | Checkout method key for hosted flows; common values include credit_card, apple_pay, google_pay, open_banking — must match a method enabled for your merchant in the admin catalogue. |
| merchant_order_id | ORDER_X9K2_7741 | Unique order reference in your system; must not collide with another pending or completed order for this merchant. |
| customer_email | [email protected] | Valid customer email (ASCII or IDN domain); used for account lookup and notifications. |
| customer_lastname | Martin | Customer family name. |
| customer_first_name | Alex | Customer given name. |
| customer_phone | +33611223344 | Phone number in international or national format as accepted by validation. |
| postal_code | 75011 | Postal or ZIP code for billing / card address. |
| city | Paris | City for billing address. |
| customer_address | 42 avenue République | Street address line. |
| birthdate | 1988-03-21 | Date of birth; use YYYY-MM-DD unless your integration sends another accepted format. |
| country | FR | ISO 3166-1 alpha-2 country code (two letters). |
| store_name | Demo Shop | Store or brand label shown to the customer during checkout. |
| success_url | https://demo.com/success | Absolute HTTPS URL to redirect after success; must be allowed for your merchant when KYB / site origin checks apply. |
| failure_url | https://demo.com/error | Absolute HTTPS URL to redirect after cancel or failure. |
The API returns success: true with an action_required value and a redirect_url. Redirect the customer to this URL.
| customer_signup | Returned for a new customer. Redirect the customer to complete signup and card checkout. |
| topup_required | Returned for an existing StixPay customer. Redirect the customer to the topup/payment page. |
| redirect_url | The URL where the customer must be redirected. |
| expires_at | Expiration date and time of the checkout or topup flow. |
Create a payment for one of your sub-merchants. Use your reseller API key and add merchant_id in the JSON body.
Trailing slash: include the final / in the base URL so POST requests are not turned into GET by redirect-follow behaviour.
| Content-Type | application/json |
| X-API-Key | Your reseller API key |
| Field | Example | Expected value |
|---|---|---|
| merchant_id | 48291 | Required for reseller keys: integer merchants.id of the sub-merchant linked to your reseller account (same field in JSON, form, or query). |
| amount | 89.99 | Positive decimal amount in major currency units. |
| currency | GBP | ISO 4217 currency code. |
| payment_method | credit_card | Checkout method key; must match an enabled method for the scoped sub-merchant (same values as merchant mode). |
| merchant_order_id | ORDER_R8P5_2203 | Unique order reference for that sub-merchant; uniqueness is enforced per merchant. |
| customer_email | [email protected] | Valid customer email. |
| customer_lastname | Dupont | Customer family name. |
| customer_first_name | Sarah | Customer given name. |
| customer_phone | +447700900123 | Phone in international or national format as accepted by validation. |
| postal_code | SW1A1AA | Postal or ZIP code for billing address. |
| city | London | City for billing address. |
| customer_address | 10 Downing Street | Street address line. |
| birthdate | 1990-11-05 | Date of birth; typically YYYY-MM-DD. |
| country | GB | ISO 3166-1 alpha-2 country code. |
| store_name | UK Demo Store | Store or brand label shown during checkout. |
| success_url | https://demo.co.uk/success | Absolute HTTPS success redirect URL (subject to the sub-merchant’s allowed origins when configured). |
| failure_url | https://demo.co.uk/failed | Absolute HTTPS cancel/failure redirect URL. |
The reseller response is the same as the merchant response. The API returns a redirect_url for the customer checkout flow.
| customer_signup | Returned when the customer does not have a StixPay account yet. |
| topup_required | Returned when the customer already exists on StixPay. |
| merchant_order_id | Your order reference for the sub-merchant payment. |
| redirect_url | The hosted StixPay URL where the customer must be redirected. |
| topup_intent_id | Returned only when the response action is topup_required. |
Configure HTTPS endpoints in the StixPay dashboard (Merchant → Webhooks). StixPay delivers each event as an HTTP POST with a JSON body. Delivery is skipped unless your endpoint subscription list includes that event type (see below).
In the dashboard, each webhook URL has its own Signing secret.
Use this value to verify webhook signatures (e.g. HMAC). Anyone signed into this merchant account can view it; protect your login.
Store the secret on your server environment (never in client-side code). If you use Regenerate secret, the old value stops working immediately — deploy the new secret before rotating, or legitimate webhooks will fail verification until your server and StixPay agree on the same key.
X-Webhook-Signature (HMAC-SHA256)StixPay sends a hex-encoded HMAC-SHA256 of the exact raw request body using that endpoint’s signing secret as the key (hash_hmac('sha256', $rawBody, $signingSecret) in PHP).
X-Webhook-Signature header using a timing-safe equality check (e.g. PHP hash_equals).401 or 403); then parse the JSON and process the event.| Content-Type | application/json |
| X-Webhook-Signature | Hex HMAC-SHA256 of the raw JSON body, keyed with this endpoint’s signing secret (see Signing secret above). |
| X-Webhook-Id | Same value as top-level id in the JSON body. |
| id | Unique delivery id (e.g. evt_…). |
| type | Event type string. Matches the subscribed key except transaction_payout_success, which uses the readable label Transaction payout success here while subscription/checkboxes still use transaction_payout_success. |
| created | Unix timestamp when the payload was assembled. |
| data | Event-specific object (below). |
The dashboard checkboxes cover: payment.succeeded, payment.failed, and transaction_payout_success. Other events in this reference are emitted only if your endpoint’s stored subscription includes their type (legacy configuration, migrations, or support-assisted setup).
type (* = UI checkbox) |
When it is sent |
|---|---|
payment.succeeded* | After a wallet/direct-acquirer payment intent is captured (customer debited; merchant payout recorded). |
payment.failed* | e.g. API intent canceled via POST …/intents/{id}/cancel, or certain on-ramp failure paths (payment_type: onramp, includes provider-facing fields). |
transaction_payout_success* | On-ramp USDC — once NxtGatew IPN confirms the merchant payout (minimal payload, no provider in data). |
payment_intent.canceled | After the merchant/platform cancels a payment intent eligible for cancellation. |
refund.completed | When a merchant-initiated refund is completed in the ledger. |
payout.completed | When an operator marks a fiat payout as completed in admin. |
kyb.approved | Platform final KYB approval. |
kyb.rejected | Platform KYB rejection (includes admin notes). |
kyb.provider_approved | Payment-rail reviewer approved the dossier. |
kyb.provider_rejected | Payment-rail reviewer rejected the dossier (includes notes). |
refund.failed, payout.failed | Appear in internal event labels for completeness; no delivery from the current PHP application code. |
gateway.payment.succeeded / gateway.payment.failed | Reserved for gateway IPN helpers (provider, source: gateway in data); no active caller in the current codebase. |
payment.succeededSent after successful capture of a payment intent. Typical data fields:
| Field | Description |
|---|---|
| payment_intent_id | Payment intent id. |
| payment_id | Captured payment row id. |
| transaction_id | Wallet/ledger transaction id. |
| amount | Captured amount. |
| fees | Fees applied at capture. |
| net_amount | Amount minus fees. |
| currency | Often EUR for wallet settlements; confirm with your integration. |
| merchant_order_id | Your order reference from intent metadata. |
| payment_type | e.g. direct_acq vs metadata-driven values. |
| settlement | standard or instant_onramp when capture used on-ramp fee rails. |
| customer_email | Customer email when resolvable. |
customer_first_name, customer_lastname, country, language / ui_locale, store_name, success_url, failure_url | From intent metadata when present. |
merchant_requested_amount, merchant_requested_currency | Present when the shopper amount was in a non-EUR currency stored on the intent. |
| timestamp | Unix time. |
payment.failedIntent cancel (Payments API) — POST …/payments/intents/{id}/cancel: expects data with payment_intent_id, reason (canceled), timestamp. That flow also emits payment_intent.canceled; subscribe to both types if you need to reconcile every lifecycle signal.
On-ramp failure: data follows the on-ramp notification shape: transaction_id, amount, fees, net_amount, currency, merchant_order_id (nullable), payment_type (onramp), settlement (instant_onramp), provider, transaction_status, gateway_payment_status, gateway_transaction_status, optional payment_intent_id, timestamp, plus merged keys such as provider_callback with the raw provider payload when applicable.
transaction_payout_success (on-ramp USDC)Fires once when NxtGatew IPN confirms the merchant USDC payout. For API on-ramp, prefer this over payment.succeeded to mark end-to-end settlement. data is intentionally minimal (no provider):
| Field | Description |
|---|---|
| transaction_id | StixPay transaction id. |
| amount | Gross ledger amount (4 decimal places). |
| net_amount | EUR-equivalent net from confirmed USDC when available; else ledger net after fees/reserve. |
| currency | Uppercase order currency (e.g. EUR, USD). |
| merchant_order_id | Your id or null. |
| payment_type | onramp |
| settlement | instant_onramp |
| transaction_status | e.g. completed |
| timestamp | Unix time. |
payment_intent.canceled| payment_intent_id | Canceled intent id. |
| timestamp | Unix time. |
refund.completed| refund_id | Refund record id. |
| payment_request_id | Related payment request. |
| amount | Refunded amount. |
| currency | e.g. USD in current implementation. |
| reason | Refund reason text. |
| customer_id | Customer user id. |
| timestamp | Unix time. |
payout.completed| payout_id | Payout id. |
| amount | Principal amount. |
| fees | Payout fees. |
| currency | Payout currency. |
| timestamp | Unix time. |
| type | Typical data |
|---|---|
kyb.approved | kyb_id, user_id, notes, timestamp |
kyb.rejected | kyb_id, user_id, notes (required rejection message), timestamp |
kyb.provider_approved | kyb_id, user_id, timestamp |
kyb.provider_rejected | kyb_id, user_id, notes, timestamp |
If a merchant belongs to a reseller program, subscribed reseller endpoints receive a copy of merchant-bound events they are subscribed to; data is extended with reseller_program: true and merchants_table_id (merchants.id for the sub-merchant).
Failed calls return JSON with success: false, a human-readable message, and often an error field (e.g. api_error). The examples below match the PHP payment stack (api/payment/payments.php) and related helpers.
If the request hits the checkout endpoint as GET (browser tab, health check, or a POST that was converted to GET after a redirect), the API responds with HTTP 200 and success: true only as a discovery hint — no payment is created. Check observed_http_method: when it is GET, send a real POST with Content-Type: application/json and your API key. Using the canonical URL https://stixpay.com/api/payment/ (with trailing slash) avoids many redirects that strip POST bodies.
{
"success": true,
"message": "Use POST with a JSON body to create a checkout. Send X-API-Key or api_key.",
"method_required": "POST",
"content_type": "application/json",
"observed_http_method": "GET",
"post_hint": "If you sent POST but saw method GET here, avoid redirects that strip POST (use URL ending with /api/payment/ or internal rewrite, not 301/302 to add slash)."
}error values (payments API)| api_error | Most validation and business rule failures. Usually HTTP 400. |
| kyb_provider_pending | Returned when message is exactly the KYB gateway block string below. HTTP 403. |
| internal_error | Non-recoverable server failure. HTTP 500. Optional debug when app debug is on. |
API key required | No X-API-Key (or query/post fallback). |
Invalid API key | Key not found or inactive. |
| Scope errors | Same string as message (see Reseller scope below). |
Wallet access is disabled for this merchant | Merchant key: wallet flag off (no trailing period in this variant). |
Wallet access is disabled for this merchant. | Reseller-scoped sub-merchant: wallet disabled (note the period). |
Demo merchant profile is invalid (missing owner_user_id). | Demo key path only. |
Merchant profile not found for this API key. | No merchant row for the key owner. |
Account suspended | Key owner user is suspended. |
Reseller API keys require merchant_id (the platform merchants.id of your sub-merchant) on every request — in the JSON body, POST fields, or query string. | Missing merchant_id. |
merchant_id is not linked to this reseller account. | Wrong merchants.id for this reseller. |
Invalid sub-merchant profile. | Linked merchant has no owner. |
Invalid payment intent. | Intent id in URL not found when checking scope. |
payment intent does not belong to the supplied merchant_id. | Intent owner ≠ scoped merchant. |
Invalid wallet link. | Wallet link id not found for scope check. |
wallet link does not belong to the supplied merchant_id. | Link owner ≠ scoped merchant. |
POST /api/payment/)Invalid JSON body | Malformed JSON or non-object body. |
The field payment_type is not accepted. Routing uses providers enabled for your account; send optional provider_code when several channels are active. | Do not send payment_type. |
Do not send merchant_wallet in the request body. The verified payout wallet from your account is applied server-side. | Remove merchant_wallet. |
wallet_link_id is not supported. Use customer_email with on-ramp or direct acquirer checkout. | Checkout flow resolver. |
Payment providers are not configured. Enable on-ramp and/or direct acquirer providers for this merchant. | Provider tables empty / not ready. |
provider_code must be one of the providers enabled for this merchant (on-ramp or direct acquirer). | Unknown provider_code. |
Several payment channels are enabled for this merchant; include provider_code in the JSON body to select an on-ramp or direct acquirer provider. | Ambiguous routing. |
No on-ramp or direct acquirer provider is enabled for this merchant. Enable at least one in the admin catalogue. | None enabled. |
Invalid endpoint or method | Wrong path or HTTP verb for this handler. |
wallet_link_id is not supported for on-ramp card checkout. | |
Invalid field for this checkout: {field} (unknown or not allowed for card on-ramp) | Extra JSON keys. |
customer_data must be a JSON object. | |
Invalid key in customer_data: {key} | |
Missing required field: {name} | Required keys include amount, currency, merchant_order_id, customer fields, URLs, etc. |
merchant_order_id is required | Empty id. |
amount must be greater than 0 | |
currency is invalid | On-ramp: empty or too short before ISO check. |
currency must be one of: EUR, USD (got: …) | Unsupported ISO code. |
No EUR exchange rate configured for {CCY}. Add a row in Admin → Exchange rates (e.g. {CCY} → EUR). | FX missing for non-EUR. |
customer_email must be a string (or use customer_data.email). | |
Invalid customer_email / Invalid customer_email: missing @ before the domain (e.g. [email protected]). | Returned by payments_api_customer_email_validation_error. |
customer_first_name and customer_lastname are required | |
country, language and store_name are required | |
success_url must be a valid absolute URL / failure_url must be a valid absolute URL | |
merchant_order_id '{id}' already exists for this merchant. | Duplicate order id. |
merchant_order_id '{id}' already has a pending on-ramp transaction. | |
Could not resolve an on-ramp provider for this merchant. | |
Resolved provider is not a valid active on-ramp catalogue entry. | |
This on-ramp provider is not wired in this environment yet (integration_key: …). | Short variant during signup path; full message may list supported keys and admin hint. |
A valid merchant Polygon wallet (0x + 40 hex) is required: add a verified Crypto (USDC ERC20) account in Bank accounts (Funds). | |
api_payment_requests is missing; ensure wallet schema is applied. | DB schema. |
Could not record API payment request: … / Could not record API payment request (invalid id). | Persist logging failure. |
Invalid provider for checkout method validation. | Resolved provider row missing during method validation. |
| Checkout method catalog | Dynamic message from checkout_payment_method_validate_catalog (unknown method, not configured, disabled, currency/region rules, etc.). |
| KYB / provider | KYB has not been approved by the payment provider for this gateway. Complete provider validation before accepting payments. — thrown by merchant_assert_payment_provider_kyb_allowed. When this exact string is the exception message, the API returns HTTP 403 and error: kyb_provider_pending. |
amount is required and must be greater than 0 | |
merchant_order_id is required | |
customer_email is required for direct acquirer checkout. | |
merchant_order_id '{id}' already exists for this merchant. Each merchant_order_id must be unique. | |
No payment provider is enabled for this merchant. Enable at least one payment provider for this account in Stixpay before creating checkout or payment sessions. | When provider tables exist. |
No payment provider is enabled for this merchant. Enable gateway access or configure payment providers before accepting API payments. | Legacy gateway path. |
Customer not found for this email. Create the customer account first, or use on-ramp checkout for first-time card payment. | |
Insufficient balance for this payment. Credit the customer account or use on-ramp card checkout. | |
| Authorize / capture | Messages from authorizePaymentIntentWithWallet / capturePaymentIntent / cancelPaymentIntent, e.g. Payment intent not found, Payment intent cannot be authorized in current status, Insufficient balance, Wallet is frozen, Transaction amount exceeds per-transaction limit, daily/monthly limit texts, Failed to capture payment, Payment intent must be authorized before capture, Capture amount cannot exceed authorized amount, Wallet not found, Failed to cancel payment, Payment intent cannot be cancelled in current status, Still insufficient funds. Missing: …, Failed to re-authorize payment, plus DB/ledger errors surfaced as message. |
Payment intent not found | Wrong id or not owned by merchant. |
Payment intent must be authorized to retry | Retry flow. |
message pattern)/api/payment/wallet-links | error: api_error — e.g. API key required, Valid email required (exact wording), link_challenge_id and otp_code required, Invalid challenge, Challenge already used or expired, OTP expired, Too many attempts, Invalid OTP code, Customer not found, Authorization token required, Invalid or expired token, Invalid endpoint or method. Balance helpers may return no_account when the customer is missing. |
/api/payment/customers | error: validation_error — e.g. API key required, Invalid API key, reseller scope strings, Valid email is required (note: is), gender/DOB validation messages, Method not allowed. |
/api/payment/topups | error: api_error — e.g. wallet_link_id is not supported. Use customer_email., Amount must be greater than 0, customer_email is required, Customer not found, Top-up intent not found. |
Note: Any database, network, or unexpected PHP error may appear as message text; treat unknown messages as generic failures and log the response for support.