Payment status and confirmations
Webhook destinations must be public HTTP(S) endpoints. Internal, loopback and reserved IP addresses are blocked, including DNS answers. Destinations are checked on every delivery and retry; redirects are not followed. Local test exceptions require an operator-configured exact endpoint.
Payment webhooks use payment_status: pending (detected, waiting for confirmations), confirmed (incoming threshold reached), and completed (distribution sent to the network, not outgoing confirmation).
UTXO, EVM and TRON report observed confirmation progress through pending, then confirmed at the configured threshold once execution checks pass. confirmations is the observed count; required_confirmations is the merchant threshold. Counts may jump, so do not require an event for every intermediate number. A threshold of one can skip pending.
Solana sends confirmed, then completed, without pending depth updates. Both confirmation fields are 1 and represent the accepted network stage, not slot depth. Custom numeric depth is not enforced. Native SOL is discovered at finalized commitment; SPL tokens at confirmed commitment.
TRON credit and distribution have separate thresholds. With confirmations=5, credit occurs at 5; distribution waits for at least 23 confirmations for TRX or 20 for TRC20 and a successful solidified receipt. A higher configured threshold delays distribution too. Pending updates stop after confirmed. Low credit thresholds do not guarantee finality or automatic reversal on a reorganization.
These fields apply to checkout, deposit, custom and subscription payment webhooks. invoice_status describes invoice settlement separately; a confirmed payment may be only a partial payment. Forwarded amounts and fees are omitted until available.
Credit each incoming transaction once at confirmed or completed. Acknowledge every event with {"status":"ok"}; deduplicate UUIDs and ignore status/count regressions because retries can arrive out of order. Event types have a status suffix, for example checkout.payment.confirmed.
Webhooks
Webhooks (also called callbacks or IPNs) are real-time notifications sent to your application. Instead of constantly checking an API for updates, your server gets notified instantly when a payment is received, confirmed, or when other important events happen.
On this page
Overview
A webhook is an HTTP request sent to your server whenever an event occurs. You provide a webhook URL when creating a payment / deposit / subscription, and updates are delivered there.
- Default delivery is GET.
post=1selects form POST;json=1selects JSON POST and takes precedence, even when both flags are enabled. - Your endpoint must reply with HTTP
2xxand JSON{"status":"ok"}. Legacy text*ok*is also accepted. - Any other response is treated as a failure and may be retried.
Amount fields and units
Amounts use ordinary currency units, not cents or blockchain base units. JSON types below belong to the signed payload; GET/form encodes them as text. Preserve types for signature verification and use decimal arithmetic for amounts.
| Context / field | JSON type | Meaning and availability |
|---|---|---|
Checkout value | string or null | Requested invoice fiat in currency, every payment stage; null when no amount is set. Not included in deposit webhooks. |
Checkout/deposit paid_amount / paid_amount_fiat | string / string or null | Incoming crypto in paid_coin / fiat in currency, before fees, every payment stage. |
Checkout/deposit received_amount / received_amount_fiat | string / string or null | Distributed crypto / fiat equivalent after fees, only at completed. |
Subscription subscription_option_value | number | Requested plan fiat in payment_currency, rounded to two decimals; 0 if unavailable. Renewal notifications; no payment_value field. |
Subscription payment_paid_amount / payment_paid_amount_fiat | number / number or null | Incoming crypto in payment_paid_coin / fiat in payment_currency, before fees, every renewal payment stage. |
Subscription payment_received_amount / payment_received_amount_fiat | number / number or null | Distributed crypto / fiat equivalent after fees, only at completed. |
Subscription list/fetch payment_list[].value | string or null | Requested invoice fiat in coin, for invoices with a detected payment; null when no amount is set. |
Subscription list/fetch payment_list[].received_fiat | string or null | Accepted incoming fiat in coin, before fees. Our commission is not deducted. Counts confirmed/completed payments once; "0" if none qualify, null if any qualifying valuation is unknown. |
Custom/Core value_coin / value_forwarded_coin | number or string | Incoming crypto before fees / distributed crypto after fees, in coin. Source network determines the type; distribution details are absent until available. |
Custom/Core fee_coin / network_fee | number, string or null | Service fee / separate network-cost deduction in payment coin or token units. Distribution details, absent or null when unknown; network deduction is not necessarily the final on-chain gas cost. |
Fiat fallback for checkout/deposit/subscription webhooks: missing incoming fiat uses the requested invoice amount if available; missing net fiat uses incoming fiat (or its invoice fallback). Both can be null. Subscription fields carry the payment_ prefix. Fallback fiat does not prove the exact transferred amount or fee; reconcile settlement using crypto amounts. List/fetch received_fiat does not use these fallbacks.
Example with known valuations: a customer pays 10 USD, total fees are 0.20 USD. Subscription history reports received_fiat: "10"; the completed renewal webhook reports payment_received_amount_fiat: 9.8. Completed does not add another 10 USD to the history.
These stage rules describe new webhooks. Logs retain stored data and historical payloads; a zero or null distribution value before completed is not proof of settlement. Core conversion fields have their own JSON-encoded string format, even in JSON transport.
HOW WEBHOOKS WORK
On small screens you can scroll horizontally to view the full sequence.
1. Create a Webhook Endpoint
Create an endpoint (URL) in your application that can receive notifications. This endpoint must:
- Be publicly accessible over HTTPS.
- Accept the configured transport: GET by default, form POST with
post=1, or JSON POST withjson=1. - Respond with HTTP
2xxand a JSON object with exactly"ok"as itsstatusvalue, for example{"status":"ok"}. Additional JSON fields are allowed. Legacy plain text*ok*is also accepted, case-insensitively and ignoring surrounding whitespace. Plainok, empty bodies and{"status":"error"}do not acknowledge delivery. Any other response will be considered a failure.
2. Provide the Endpoint URL
When you make an API request to create a payment, deposit, or subscription, provide your endpoint URL:
notify_url(for Checkout)callback(for Custom Payment Flow)
Example (Custom Payment Flow)
curl --get "https://api-service.kingpay.online/btc/create/" -H "apikey: YOUR_API_KEY" --data-urlencode "callback=https://yoursite.com/webhook"
URL encode your webhook URL if it contains its own query parameters. For example, https://yoursite.com/webhook?order_id=123 becomes https%3A//yoursite.com/webhook%3Forder_id%3D123.
const originalUrl = 'https://yoursite.com/webhook?order_id=123'; const encodedUrl = encodeURIComponent(originalUrl); // encodedUrl: "https%3A%2F%2Fyoursite.com%2Fwebhook%3Forder_id%3D123"
3. Secure Your Endpoint
CRITICAL: You must verify webhook signatures
This is essential to ensure the notification was sent by the provider and has not been tampered with. Validate every webhook request before processing it.
Webhook Payloads
The data fields included in a webhook notification depend on the service and the event that occurred. For example, a webhook for a new deposit will have different information than a webhook for a completed checkout payment.
Refer to the specific documentation for payload details:
- Custom Payment Flow Webhooks
- Checkout Webhooks
- Deposit Webhooks
- Subscription Webhooks
Best Practices & Troubleshooting
To ensure your webhook integration is robust, follow these best practices:
- Respond quickly (within 30 seconds): acknowledge webhooks with HTTP
2xxand{"status":"ok"}. For longer processing, handle it asynchronously after responding. - Handle retries with idempotency: your endpoint may receive the same webhook multiple times. Log processed webhook
uuidand ignore duplicates. - Verify the webhook signature: use your project's IPN secret and the signature headers. Do not rely on IP addresses copied from another service.
- Use ngrok for local testing: expose your local development server to test without deploying.
Retry Schedule
Notifications are saved before sending. The first attempt starts as soon as a worker is available. Failed deliveries are retried automatically for 48 hours from notification creation:
- After failures: 1 minute, 5 minutes, 15 minutes, then 1 hour.
- Subsequent delays: 3 hours. Each delay starts when the preceding attempt finishes.
- HTTP errors, invalid acknowledgements and 30-second timeouts trigger retries. Redirects are not followed.
- Retries preserve the payload and UUID. Each attempt has a fresh HMAC signature timestamp using the current project secret.
- Queued notifications survive restarts. Duplicate or out-of-order delivery is possible: acknowledge an already processed UUID without applying the payment again.
- After 48 hours, automatic delivery ends with
failed. An in-flight attempt may finish. - The dashboard shows delivery state, attempt history and next retry time. Manual resend remains available to the project owner.
- A manual resend does not extend the original 48-hour deadline. After the deadline it permits one additional attempt; within the window automatic retries can continue after a failure.
- Historical logs are not replayed automatically. They can be resent manually from the dashboard.
Troubleshooting Common Issues
- Webhooks not received: URL not publicly accessible, firewall/Cloudflare blocking requests, or customer sends a different coin/token than the generated address.
- Duplicate processing: retries are not handled idempotently. Always check webhook
uuidbefore processing.