Accept + settle
Confirm delivery, then distribute funds with cashOutAmountList. Two calls, one sum-to-itemPrice rule, and you're done.
- 1Auth
- 2Users
- 3Contract
- 4Payment
- 5Settle
Accept delivery
After payment, the milestone is in WAITING_FOR_DELIVERY. The admin confirms delivery succeeded. Note actorRole: BROKER — ACCEPT and REJECT are broker-side actions.
curl -s "$WAFFY_BASE_URL/contract-actions/external" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"contractId\": \"$MILESTONE_ID\",
\"userId\": \"$WAFFY_ADMIN_ID\",
\"actorRole\": \"BROKER\",
\"contractAction\": \"ACCEPT_CONTRACT\",
\"contractType\": \"MILESTONE_CONTRACT\"
}"Status flow after accept:
If inspection is not enabled for your org, the flow goes directly from accept to RESOLVED_RELEASE_PROVIDER.
Or reject delivery
If delivery failed, reject instead — funds route back to the buyer on settlement.
curl -s "$WAFFY_BASE_URL/contract-actions/external" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"contractId\": \"$MILESTONE_ID\",
\"userId\": \"$WAFFY_ADMIN_ID\",
\"actorRole\": \"BROKER\",
\"contractAction\": \"REJECT_CONTRACT\",
\"contractType\": \"MILESTONE_CONTRACT\"
}"Status flow after reject:
Settle
Distributes funds. cashOutAmountList must sum to itemPrice exactly. Note actorRole: CLIENT_ADMIN on SETTLE (only) — ACCEPT/REJECT use BROKER.
curl -s "$WAFFY_BASE_URL/contract-actions/external" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"contractId\": \"$MILESTONE_ID\",
\"userId\": \"$WAFFY_ADMIN_ID\",
\"actorRole\": \"CLIENT_ADMIN\",
\"receiverId\": \"$SELLER_ID\",
\"senderId\": \"$WAFFY_ADMIN_ID\",
\"contractType\": \"MILESTONE_CONTRACT\",
\"contractAction\": \"SETTLE_CONTRACT\",
\"cashOutAmountList\": [
{ \"id\": \"$SELLER_ID\", \"amountDue\": 4800 },
{ \"id\": \"$WAFFY_ADMIN_ID\", \"amountDue\": 200 }
]
}"Fee deduction rules
Waffy's fee is deducted from your org's cut of the transaction — not from the seller's or buyer's payout. If your cut is 0, Waffy applies a negative balance deducted from future revenue.
Walkthrough complete
You've built a one-milestone escrow end to end. The next step is picking the integration pattern that matches what your product needs — every pattern reuses the same 5 steps you just built, only the parties and cashOutAmountList blocks change.
Common mistakes
| Mistake | Fix |
|---|---|
Using user_token for startPayment | Use customer_token — buyer's token only |
Not appending customer_token to the payment URL | Required for raw redirect (Option A) |
senderRole: PROVIDER on milestone | Must be BROKER on parent and every milestone |
Sending password in sign-up body | Don't. Store the returned clientUserToken |
Business logic keyed off totalAmount | Use itemPrice everywhere (parties, cashOutAmountList) |
Forgetting isSender: true on BROKER | BROKER must have both isSender: true and arbitrator: true |
actorRole: CLIENT_ADMIN on ACCEPT/REJECT | ACCEPT/REJECT use BROKER. Only SETTLE uses CLIENT_ADMIN |
cashOutAmountList doesn't sum to itemPrice | Must equal itemPrice exactly |
paymentMethods in milestone | Remove — configured at org level during onboarding |
isDeliverable / isInspectable in milestone | Remove — these are org-level settings, not per-milestone |
isSender on PROVIDER | Omit entirely — sending it is rejected by the API |
Calling ACCEPT/REJECT outside WAITING_FOR_DELIVERY | Only valid in that status — check milestone status before acting |
| Not verifying webhook signature | Always HMAC-SHA256 verify Waffy-Signature before processing |
| Slow webhook response | Respond HTTP 200 immediately, process async |