Build a Payment · Step 5 of 5

Accept + settle

Confirm delivery, then distribute funds with cashOutAmountList. Two calls, one sum-to-itemPrice rule, and you're done.

  1. 1Auth
  2. 2Users
  3. 3Contract
  4. 4Payment
  5. 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.

bash
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:

WAITING_FOR_DELIVERY
ACCEPT_CONTRACT
ITEM_INSPECTION (if org has inspection enabled)
RESOLVED_RELEASE_PROVIDER (ready to settle)

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.

bash
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:

WAITING_FOR_DELIVERY
REJECT_CONTRACT
RESOLVED_REFUND_CUSTOMER (ready to settle → cash out to buyer)

Settle

Distributes funds. cashOutAmountList must sum to itemPrice exactly. Note actorRole: CLIENT_ADMIN on SETTLE (only) — ACCEPT/REJECT use BROKER.

bash
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

MistakeFix
Using user_token for startPaymentUse customer_token — buyer's token only
Not appending customer_token to the payment URLRequired for raw redirect (Option A)
senderRole: PROVIDER on milestoneMust be BROKER on parent and every milestone
Sending password in sign-up bodyDon't. Store the returned clientUserToken
Business logic keyed off totalAmountUse itemPrice everywhere (parties, cashOutAmountList)
Forgetting isSender: true on BROKERBROKER must have both isSender: true and arbitrator: true
actorRole: CLIENT_ADMIN on ACCEPT/REJECTACCEPT/REJECT use BROKER. Only SETTLE uses CLIENT_ADMIN
cashOutAmountList doesn't sum to itemPriceMust equal itemPrice exactly
paymentMethods in milestoneRemove — configured at org level during onboarding
isDeliverable / isInspectable in milestoneRemove — these are org-level settings, not per-milestone
isSender on PROVIDEROmit entirely — sending it is rejected by the API
Calling ACCEPT/REJECT outside WAITING_FOR_DELIVERYOnly valid in that status — check milestone status before acting
Not verifying webhook signatureAlways HMAC-SHA256 verify Waffy-Signature before processing
Slow webhook responseRespond HTTP 200 immediately, process async