Skip to main content
Whichever way the customer paid, your server decides an order is paid, never the browser. This page is the checklist.

The rule

Fulfil an order only when a server-side GET /v1/ugwo/{uid} returns:
  • status is ugwo_successful
  • total_amount_paid ≥ amount
  • amount, currency and ref match the order in your database
Trigger that check from the ugwo.successful webhook, and from a periodic reconciliation job as a safety net.

Why not trust the browser?

onSuccess in the embed, a returnUrl redirect, or your own frontend polling all run on the customer’s device. They can be closed before they fire, replayed, or forged. Use them to update the screen (“Payment received, confirming…”), not to release goods.

Step by step

1

Verify the webhook signature

Compute HMAC-SHA256(secret, raw_body) and compare it in constant time with X-Kwugwo-Signature. Reject mismatches with 401. See Verifying the signature.
2

Acknowledge and de-duplicate

Store the envelope uid (evt.…); if you’ve seen it, return 200 and stop. Otherwise queue the work and return 200 quickly.
3

Find your order

Look it up by data.ref (your order ID) or by data.uid (the ugwo ID you stored when you created it). Unknown? Log it and stop.
4

Re-fetch the ugwo

Call GET /v1/ugwo/{uid}. The webhook data is a snapshot from when the event fired; the API is the source of truth.
5

Compare against the order

Check status, amounts, currency and ref as in the rule. Any mismatch: don’t fulfil, flag the order for review.
6

Fulfil exactly once

Move the order from pending to paid in a single conditional update (e.g. UPDATE … WHERE id = ? AND status = 'pending'). If no row changed, it was already fulfilled.

Example handler

Edge cases

If the PSP reports collecting less than amount (for example a short bank transfer), the activity is successful but the ugwo goes back to requires_ugwo and total_amount_paid shows what arrived. No ugwo.successful is sent. A new attempt asks for the full amount again, so refund the short payment or settle the difference with the customer directly.
A customer can pay into an old virtual account after the order is already paid. You receive ugwo.activity.double_charge_detected and the activity has is_double_charge: true. The ugwo is unchanged. Refund that activity.
Run a reconciliation job every few minutes: for orders still pending after, say, 10 minutes, call GET /v1/ugwo/{uid} and apply the same checks. Webhooks are retried three times, 30 minutes apart, so don’t rely on them alone.
It was paid, then (partly) refunded. If you haven’t fulfilled yet, treat it as needing review rather than shipping.
A PSP can reverse a payment it had confirmed. You receive ugwo.activity.updated with data.status: "reversed". Hold or claw back the order according to your policy.

Store these IDs on your order