Webhooks

Webhooks provide real-time, server-to-server notifications when the status of a payment changes. Instead of polling the API, your application receives a POST request at a URL you configure when creating a Quick Pay payment.

Why use webhooks?

  • Speed: The payment succeeded webhook often arrives seconds before the customer is redirected back to your site, allowing you to begin fulfillment immediately.
  • Reliability: If a customer closes their browser before reaching your redirect_url, the webhook ensures you still receive the final payment status.
  • Security: Webhooks are sent directly from Wonderful's servers to yours — they are not visible to the client and cannot be tampered with.

How it works

  1. Include a webhook_url parameter when creating a Quick Pay payment.
  2. Wonderful sends a POST request to that URL at key moments during the payment lifecycle.
  3. Your server responds with 200 OK to acknowledge receipt. Any other response triggers retries.
  4. Always verify the status by calling GET /v2/payments/{id} — the webhook payload is informative, but the API is the single source of truth.

Webhook payload

{
    "wonderful_payment_id": "01eeff8a-abf0-4011-8978-1983868a4343",
    "payment_type": "domestic-payments",
    "status": "paid"
}

Events that trigger a webhook


Creating a payment with a webhook URL

Add the webhook_url field when calling POST /v2/quick-pay.

curl --request POST \
    "https://api.wonderful.one/v2/quick-pay" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data '{
        "amount": 1000,
        "merchant_payment_reference": "ORDER-1234",
        "redirect_url": "https://mysite.com/success",
        "webhook_url": "https://mysite.com/webhooks/wonderful"
    }'
fetch("https://api.wonderful.one/v2/quick-pay", {
    method: "POST",
    headers: {
        "Authorization": "Bearer {YOUR_AUTH_KEY}",
        "Content-Type": "application/json",
        "Accept": "application/json",
    },
    body: JSON.stringify({
        amount: 1000,
        merchant_payment_reference: "ORDER-1234",
        redirect_url: "https://mysite.com/success",
        webhook_url: "https://mysite.com/webhooks/wonderful",
    }),
});
$client = new \GuzzleHttp\Client();
$response = $client->post('https://api.wonderful.one/v2/quick-pay', [
    'headers' => [
        'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
    ],
    'json' => [
        'amount' => 1000,
        'merchant_payment_reference' => 'ORDER-1234',
        'redirect_url' => 'https://mysite.com/success',
        'webhook_url' => 'https://mysite.com/webhooks/wonderful',
    ],
]);

Verifying the payment status

When your webhook endpoint receives a notification, fetch the authoritative status from the API:

curl --request GET \
    --get "https://api.wonderful.one/v2/payments/01eeff8a-abf0-4011-8978-1983868a4343" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
$client = new \GuzzleHttp\Client();
$paymentId = $payload['wonderful_payment_id'];
$response = $client->get("https://api.wonderful.one/v2/payments/{$paymentId}", [
    'headers' => [
        'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
    ],
]);
$body = json_decode((string) $response->getBody());
$authoritativeStatus = $body->data->status;

Only act on completed status — intermediate statuses (created, pending) may arrive before the payment is final.


Building the webhook listener (generic)

POST /webhooks/wonderful

Request body:
{
    "wonderful_payment_id": "01eeff8a-abf0-4011-8978-1983868a4343",
    "payment_type": "domestic-payments",
    "status": "paid"
}

Your endpoint should:

  1. Validate that wonderful_payment_id is present.
  2. Call GET /v2/payments/{id} to get the authoritative status.
  3. Update your local records based on the authoritative status.
  4. Return 200 OK to acknowledge receipt (prevents retries).

Node.js (Express) example

app.post("/webhooks/wonderful", async (req, res) => {
    const paymentId = req.body.wonderful_payment_id;

    if (!paymentId) {
        return res.status(200).json({ status: "error" });
    }

    const response = await fetch(
        `https://api.wonderful.one/v2/payments/${paymentId}`,
        {
            headers: {
                Authorization: "Bearer {YOUR_AUTH_KEY}",
                "Content-Type": "application/json",
                Accept: "application/json",
            },
        }
    );
    const data = await response.json();
    const status = data.data.status;

    if (status === "completed") {
        // Update order status in your database
        // Trigger fulfillment
    }

    res.status(200).json({ status: "ok" });
});

PHP example (no framework)

<?php
$payload = json_decode(file_get_contents('php://input'), true);
$paymentId = $payload['wonderful_payment_id'] ?? null;

if (!$paymentId) {
    http_response_code(200);
    echo json_encode(['status' => 'error']);
    exit;
}

$client = new \GuzzleHttp\Client();
$response = $client->get(
    "https://api.wonderful.one/v2/payments/{$paymentId}",
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = json_decode((string) $response->getBody());

if ($body->data->status === 'completed') {
    // Update order, trigger fulfillment
}

http_response_code(200);
echo json_encode(['status' => 'ok']);

Webhook best practices

  1. Always verify via the API. The webhook payload status may be stale — the API is the source of truth.
  2. Respond quickly. Return 200 OK as soon as you have validated and stored the notification. Heavy processing can be deferred to a queue.
  3. Return 500 to trigger a retry. If your database or the API call fails, return a non-200 status so Wonderful retries the webhook.
  4. Idempotency. Your webhook handler should be safe to run multiple times. A given payment may send multiple webhook notifications.
  5. Log everything. Record the webhook receipt, the API verification call, and any resulting actions for debugging.

Limitations

Webhooks are currently available only for payments created via the Quick Pay endpoint (POST /v2/quick-pay). Payments made through reusable QR code links do not currently support webhooks.