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
- Include a
webhook_urlparameter when creating a Quick Pay payment. - Wonderful sends a
POSTrequest to that URL at key moments during the payment lifecycle. - Your server responds with
200 OKto acknowledge receipt. Any other response triggers retries. - 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
- Customer selects their bank
- Customer consents to the payment
- Customer is redirected to their banking app
- Customer returns from their banking app
- Wonderful begins processing the payment
- Payment succeeds
- Payment fails
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:
- Validate that
wonderful_payment_idis present. - Call
GET /v2/payments/{id}to get the authoritative status. - Update your local records based on the authoritative status.
- Return
200 OKto 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
- Always verify via the API. The webhook payload status may be stale — the API is the source of truth.
- Respond quickly. Return
200 OKas soon as you have validated and stored the notification. Heavy processing can be deferred to a queue. - Return
500to trigger a retry. If your database or the API call fails, return a non-200 status so Wonderful retries the webhook. - Idempotency. Your webhook handler should be safe to run multiple times. A given payment may send multiple webhook notifications.
- 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.