Skip to main content

Webhooks

Webhooks allow 4pay.online to push real-time transaction status updates to your server — no polling required.


How it works

  1. When creating a transaction, pass a notifyUrl in the request body
  2. When the transaction status changes, the platform sends a POST request to your notifyUrl
  3. Your server responds with {"success": "true"} to acknowledge receipt
  4. If your server does not respond with a success, the platform will retry

Setting up a webhook endpoint

Option 1 — Per transaction

Pass notifyUrl when creating each transaction:

{
"params": {
"type": "payment",
"amount": 1000,
"currency": "USD",
"txid": "order-12345",
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail",
"notifyUrl": "https://yoursite.com/webhooks/payment"
}
}

Option 2 — Terminal-level default

Your platform operator can configure a default notify_url at the terminal level in the Admin Console. This applies to all transactions on that terminal unless overridden per-request.


Webhook payload

{
"type": "payment",
"txid": "order-12345",
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 1000,
"amount_dest": 950,
"amount_with_fee": 1050,
"status": "charged",
"error_description": ""
}

Fields

FieldTypeDescription
typestringEvent type: payment, payout
txidstringYour original order ID
idstringPlatform transaction UUID
amountintegerOriginal amount in smallest currency unit
amount_destintegerAmount credited to you (after fees)
amount_with_feeintegerAmount including platform fee
statusstringNew transaction status
error_descriptionstringError description (empty on success)

Status values in webhooks

StatusMeaning
startedPayment initiated
chargedPayment successful
failedPayment failed
refundedRefund completed
cancelledTransaction cancelled

Responding to a webhook

Your endpoint must respond with HTTP 200 and the following JSON body:

{ "success": "true" }

Any other response (non-200 status, different body, timeout) is treated as a failure and will trigger a retry.

Respond quickly

Process the webhook asynchronously. Acknowledge receipt immediately and handle business logic in a background job. Long-running handlers may time out and cause duplicate deliveries.


Retry policy

If your endpoint fails to respond successfully, the platform retries delivery with exponential backoff. Ensure your webhook handler is idempotent — the same event may be delivered more than once.

To deduplicate events, use the id field (transaction UUID) as a unique key.


Testing webhooks locally

Use a tunnel tool (ngrok, localtunnel, Cloudflare Tunnel) to expose your local server to the internet during development:

ngrok http 3000
# → Forwarding: https://abc123.ngrok.io → localhost:3000

Use https://abc123.ngrok.io/webhooks/payment as your notifyUrl in test transactions.

See Sandbox & Testing for test transaction instructions.


Example handler (Node.js)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhooks/payment', (req, res) => {
const { type, txid, id, status, amount } = req.body;

// Deduplicate by transaction ID
// processPaymentEvent(id, status, amount);

console.log(`Transaction ${txid} (${id}): ${status}`);

// Acknowledge receipt
res.json({ success: 'true' });
});

app.listen(3000);

Example handler (Python)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/payment', methods=['POST'])
def webhook():
data = request.get_json()
tx_id = data.get('id')
status = data.get('status')
txid = data.get('txid')

# Deduplicate by tx_id and handle event
print(f"Transaction {txid} ({tx_id}): {status}")

return jsonify({"success": "true"})