Documentation

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).

Getting started

Merchant

Create a payment and redirect the customer.

Reseller

Same API with merchant_id.

Webhooks

Merchant webhooks: payment.succeeded, payment.failed, transaction_payout_success, plus KYB, refunds, payouts, and more — see Webhooks.

POST

Merchant - Create Payment

https://stixpay.com/api/payment/

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.

Headers
Content-Typeapplication/json
X-API-KeyYour merchant API key
Body Params
Field Example Expected value
amount47.25Positive decimal amount in major currency units (not cents).
currencyUSDISO 4217 currency code (e.g. USD, EUR, GBP).
payment_methodcredit_cardCheckout 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_idORDER_X9K2_7741Unique 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_lastnameMartinCustomer family name.
customer_first_nameAlexCustomer given name.
customer_phone+33611223344Phone number in international or national format as accepted by validation.
postal_code75011Postal or ZIP code for billing / card address.
cityParisCity for billing address.
customer_address42 avenue RépubliqueStreet address line.
birthdate1988-03-21Date of birth; use YYYY-MM-DD unless your integration sends another accepted format.
countryFRISO 3166-1 alpha-2 country code (two letters).
store_nameDemo ShopStore or brand label shown to the customer during checkout.
success_urlhttps://demo.com/successAbsolute HTTPS URL to redirect after success; must be allowed for your merchant when KYB / site origin checks apply.
failure_urlhttps://demo.com/errorAbsolute HTTPS URL to redirect after cancel or failure.
Response

The API returns success: true with an action_required value and a redirect_url. Redirect the customer to this URL.

customer_signupReturned for a new customer. Redirect the customer to complete signup and card checkout.
topup_requiredReturned for an existing StixPay customer. Redirect the customer to the topup/payment page.
redirect_urlThe URL where the customer must be redirected.
expires_atExpiration date and time of the checkout or topup flow.
POST

Reseller - Create Payment

https://stixpay.com/api/payment/

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.

Headers
Content-Typeapplication/json
X-API-KeyYour reseller API key
Body Params
Field Example Expected value
merchant_id48291Required for reseller keys: integer merchants.id of the sub-merchant linked to your reseller account (same field in JSON, form, or query).
amount89.99Positive decimal amount in major currency units.
currencyGBPISO 4217 currency code.
payment_methodcredit_cardCheckout method key; must match an enabled method for the scoped sub-merchant (same values as merchant mode).
merchant_order_idORDER_R8P5_2203Unique order reference for that sub-merchant; uniqueness is enforced per merchant.
customer_email[email protected]Valid customer email.
customer_lastnameDupontCustomer family name.
customer_first_nameSarahCustomer given name.
customer_phone+447700900123Phone in international or national format as accepted by validation.
postal_codeSW1A1AAPostal or ZIP code for billing address.
cityLondonCity for billing address.
customer_address10 Downing StreetStreet address line.
birthdate1990-11-05Date of birth; typically YYYY-MM-DD.
countryGBISO 3166-1 alpha-2 country code.
store_nameUK Demo StoreStore or brand label shown during checkout.
success_urlhttps://demo.co.uk/successAbsolute HTTPS success redirect URL (subject to the sub-merchant’s allowed origins when configured).
failure_urlhttps://demo.co.uk/failedAbsolute HTTPS cancel/failure redirect URL.
Response

The reseller response is the same as the merchant response. The API returns a redirect_url for the customer checkout flow.

customer_signupReturned when the customer does not have a StixPay account yet.
topup_requiredReturned when the customer already exists on StixPay.
merchant_order_idYour order reference for the sub-merchant payment.
redirect_urlThe hosted StixPay URL where the customer must be redirected.
topup_intent_idReturned only when the response action is topup_required.

Merchant webhooks

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).

Signing secret

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.

Verifying 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).

  1. Read the POST body as raw bytes/string before parsing JSON. Do not rebuild the body from a decoded object — encoding, key order, or spaces would break the signature.
  2. Compute HMAC-SHA256 with your signing secret and compare the result (hex) to the X-Webhook-Signature header using a timing-safe equality check (e.g. PHP hash_equals).
  3. Reject requests with a missing or mismatched signature (respond 401 or 403); then parse the JSON and process the event.
HTTP headers (every POST)
Content-Typeapplication/json
X-Webhook-SignatureHex HMAC-SHA256 of the raw JSON body, keyed with this endpoint’s signing secret (see Signing secret above).
X-Webhook-IdSame value as top-level id in the JSON body.
JSON envelope (all events)
idUnique delivery id (e.g. evt_…).
typeEvent 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.
createdUnix timestamp when the payload was assembled.
dataEvent-specific object (below).
What you can subscribe to (merchant UI)

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).

Event catalog
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.canceledAfter the merchant/platform cancels a payment intent eligible for cancellation.
refund.completedWhen a merchant-initiated refund is completed in the ledger.
payout.completedWhen an operator marks a fiat payout as completed in admin.
kyb.approvedPlatform final KYB approval.
kyb.rejectedPlatform KYB rejection (includes admin notes).
kyb.provider_approvedPayment-rail reviewer approved the dossier.
kyb.provider_rejectedPayment-rail reviewer rejected the dossier (includes notes).
refund.failed, payout.failedAppear in internal event labels for completeness; no delivery from the current PHP application code.
gateway.payment.succeeded / gateway.payment.failedReserved for gateway IPN helpers (provider, source: gateway in data); no active caller in the current codebase.
payment.succeeded

Sent after successful capture of a payment intent. Typical data fields:

FieldDescription
payment_intent_idPayment intent id.
payment_idCaptured payment row id.
transaction_idWallet/ledger transaction id.
amountCaptured amount.
feesFees applied at capture.
net_amountAmount minus fees.
currencyOften EUR for wallet settlements; confirm with your integration.
merchant_order_idYour order reference from intent metadata.
payment_typee.g. direct_acq vs metadata-driven values.
settlementstandard or instant_onramp when capture used on-ramp fee rails.
customer_emailCustomer email when resolvable.
customer_first_name, customer_lastname, country, language / ui_locale, store_name, success_url, failure_urlFrom intent metadata when present.
merchant_requested_amount, merchant_requested_currencyPresent when the shopper amount was in a non-EUR currency stored on the intent.
timestampUnix time.
payment.failed

Intent 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):

FieldDescription
transaction_idStixPay transaction id.
amountGross ledger amount (4 decimal places).
net_amountEUR-equivalent net from confirmed USDC when available; else ledger net after fees/reserve.
currencyUppercase order currency (e.g. EUR, USD).
merchant_order_idYour id or null.
payment_typeonramp
settlementinstant_onramp
transaction_statuse.g. completed
timestampUnix time.
payment_intent.canceled
payment_intent_idCanceled intent id.
timestampUnix time.
refund.completed
refund_idRefund record id.
payment_request_idRelated payment request.
amountRefunded amount.
currencye.g. USD in current implementation.
reasonRefund reason text.
customer_idCustomer user id.
timestampUnix time.
payout.completed
payout_idPayout id.
amountPrincipal amount.
feesPayout fees.
currencyPayout currency.
timestampUnix time.
KYB events
typeTypical data
kyb.approvedkyb_id, user_id, notes, timestamp
kyb.rejectedkyb_id, user_id, notes (required rejection message), timestamp
kyb.provider_approvedkyb_id, user_id, timestamp
kyb.provider_rejectedkyb_id, user_id, notes, timestamp
Reseller programs

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).

Error Responses

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.

GET or redirect — not a created payment

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)."
}
Top-level error values (payments API)
api_errorMost validation and business rule failures. Usually HTTP 400.
kyb_provider_pendingReturned when message is exactly the KYB gateway block string below. HTTP 403.
internal_errorNon-recoverable server failure. HTTP 500. Optional debug when app debug is on.
Authentication & merchant profile
API key requiredNo X-API-Key (or query/post fallback).
Invalid API keyKey not found or inactive.
Scope errorsSame string as message (see Reseller scope below).
Wallet access is disabled for this merchantMerchant 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.
Reseller API key scope
Account suspendedKey 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.
Request body & routing (POST /api/payment/)
Invalid JSON bodyMalformed 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 methodWrong path or HTTP verb for this handler.
On-ramp (card) checkout
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 requiredEmpty id.
amount must be greater than 0
currency is invalidOn-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 catalogDynamic message from checkout_payment_method_validate_catalog (unknown method, not configured, disabled, currency/region rules, etc.).
KYB / providerKYB 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.
Direct acquirer (wallet) checkout
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 / captureMessages 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 intents sub-routes
Payment intent not foundWrong id or not owned by merchant.
Payment intent must be authorized to retryRetry flow.
Other JSON APIs (same message pattern)
/api/payment/wallet-linkserror: 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/customerserror: 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/topupserror: 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.