Take payment
Get the hosted-checkout URL with the user_token, hand it to the buyer, then wait for the PAID webhook.
- 1Auth
- 2Users
- 3Contract
- 4Payment
- 5Settle
Start payment
Use USER_TOKEN , not CUSTOMER_TOKEN. This returns a hosted checkout URL.
curl -s "$WAFFY_BASE_URL/api/external/contracts/startPayment/$MILESTONE_ID/$WAFFY_CLIENT_ID?redirectUrl=https://yourapp.com/done&paymentType=PURCHASE" \ -H "Authorization: Bearer $CUSTOMER_TOKEN"
Response:
{
"data": "https://app.waffyapp.com/external/UXFmUCaG?client_id=...&payment_methods=MADA,VISA,APPLE_PAY"
}payment_methods is auto-populated from your org's contracted methods — you do not set it.
Two ways to present the checkout URL — pick one based on your front-end stack.
Option A — Raw URL redirect (Flutter and frameworks the SDK doesn't cover)
Append userTokenUrl as a query parameter to the returned URL and redirect the buyer there.
const url = paymentUrl + "&userTokenUrl=" + CUSTOMER_TOKEN; window.location.href = url; // or Flutter: launchUrl(Uri.parse(url))
Option B — Waffy SDK (recommended for Web, Android, iOS, React Native)
The SDK handles the checkout in a modal / popup / iframe and manages auth for you. Include the SDK script from https://sdk.waffyapp.com/v2/waffy-payment-display.min.js in your page, then call:
WaffyPaymentDisplay.show({
paymentUrl: "https://app.waffyapp.com/external/UXFmUCaG?client_id=...",
userToken: CUSTOMER_TOKEN,
mode: "modal" // or "popup" or "iframe"
});Android / iOS / React Native use the platform equivalents of the same SDK — your integration team receives the package references during onboarding.
Handle the PAID event
When the buyer completes payment, you will receive:
{
"contractId": "607f1f77bcf86cd799439022",
"status": "PAID",
"referenceId": "txn_abc123"
}contractId is the milestone ID. Always verify the Waffy-Signature header before processing.
Webhook URL is configured by Waffy at onboarding
You don't PATCH it yourself. Give your endpoint URL to your account manager during sandbox set-up and again before go-live. If it ever needs to change, email developers@waffyapp.com.
Verify the webhook signature
Every webhook request carries a Waffy-Signature header. Compute HMAC-SHA256 of the raw request body using your webhook secret and compare. Reject any request that does not match.
# Header format
Waffy-Signature: sha256=abc123def456...
# Verification (Node.js example)
const expected = "sha256=" + crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
if (expected !== req.headers["waffy-signature"]) {
return res.status(401).send("Signature mismatch");
}Retry policy
Waffy retries failed webhook deliveries 3 times: after 1 s, 10 s, and 100 s. Your endpoint must respond HTTP 200 immediately — process the event asynchronously.
Respond 200 first, process second
If your endpoint takes more than a few seconds to respond, Waffy will mark the delivery as failed and retry. Acknowledge immediately with HTTP 200, then enqueue the event for async processing.
Webhook status values
| Status | Meaning |
|---|---|
PAID | Buyer paid — funds are in escrow. Move to delivery. |
CASHOUT_IN_PROGRESS | Funds are being released to parties. No action needed. |
COMPLETED | Contract is fully settled. All parties have been paid out. |