The rule
Fulfil an order only when a server-side
GET /v1/ugwo/{uid}
returns:statusisugwo_successfultotal_amount_paid≥amountamount,currencyandrefmatch the order in your database
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
The customer paid less than the amount
The customer paid less than the amount
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.The customer paid twice
The customer paid twice
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.The webhook never arrived
The webhook never arrived
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.The ugwo is partially_refunded or refunded
The ugwo is partially_refunded or refunded
It was paid, then (partly) refunded. If you haven’t fulfilled yet,
treat it as needing review rather than shipping.
A payment was reversed
A payment was reversed
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.