How it differs from a normal escrow
A normal escrow releases all-or-nothing: the buyer funds it, thencomplete pays out the whole amount and the escrow ends. A progressive escrow instead:
- stays in
paidstatus across many payouts, - decrements its balance on each release,
- accepts top-ups with
add_fundsat any time, - and only reaches
completedwhen you explicitly close it.
The flow
1
Create the escrow with progressive release
Include a
progressiveRelease object on Create Escrow. Presence of the object is the opt-in — do not send enabled. Optionally set autoApproveHours (the client’s review window) within the platform bounds.2
The client funds the escrow and accepts automatic release
Send the client to an
accept_pay checkout session. Because undisputed work is released automatically, DHMAD requires the client to explicitly accept that term before funding. This consent is recorded against the window they were shown, so changing autoApproveHours later re-prompts rather than silently altering the deal.3
Claim each period as work is delivered
At the end of each billing period, your backend calls Create Payout Request with a
requestId and the amount. You may do this on the seller’s behalf — the freelancer takes no weekly action.Send either ledger amountTnd or face amount + currency (e.g. 24.00 USD). DHMAD converts face claims with the escrow’s locked FX — you do not need to invent TND yourself.The amount is reserved against the balance immediately, so two periods can never be backed by the same funds.4
The client approves, rejects, or does nothing
- Approve (
approve_releasecheckout session) — the slice is released to the seller’s DHMAD balance immediately. - Reject (
reject_releasecheckout session) — a dispute opens for that period only. The amount stays reserved and protected while DHMAD reviews it. Later periods keep running. - Nothing — the request auto-approves and releases once
approvalDeadlinepasses.
5
Top up when the balance runs low
When no unreserved balance is left, DHMAD emits
escrow.balance.exhausted and the escrow stays open. Pause tracking, prompt the client to top up with an add_funds checkout session, and resume. Do not close the contract.6
Close the contract when the work ends
Close Escrow settles any approved-but-unpaid periods, then refunds the genuinely unused balance and completes the escrow with
completionSource: "progressive_close".Remaining vs funded
Liveamount / amountTnd are the remaining float — they shrink on each release. They are not the original funded principal.
Do not invent face amounts with
tnd / fxRateUsed. DHMAD already emits face balances using the same pro-rata slice math as settlement.
Reservations (face + TND)
Reservation accounting is what makes concurrent periods safe. Progressive GET and balance webhooks include both currencies:
On open escrows:
availableForReleaseTnd = amountTnd − reservedTnd (and the face pair mirrors that in currency).
Pending, approved-but-unpaid, and disputed requests all hold their reservation. Only released and voided free it. Consequently:
- A refund or cancellation can never take funds a period still has a claim on.
- Closing is refused while any period is undecided.
Terminal snapshot
Oncompleted / cancelled (including escrow.status.updated and Get Escrow):
availableForRelease*=0,reserved*=0,remaining*=0releasedTotal*= what was actually paid to the sellerrefundedTotal*= unused balance returned
Disputes are scoped to one period
A client rejection always opens a dispute. This is deliberate: if rejection merely parked the money, a client could block a freelancer’s payment indefinitely with no path to resolution. That dispute is scoped to the rejected period. It does not freeze the escrow, so later periods, top-ups, and the auto-release timer all continue. DHMAD then awards all, part, or none of the claim; any unawarded remainder returns to the available balance and can fund future work or be refunded at closure.Fees and invoicing
Platform and developer fees are apportioned to each slice, so a sequence of partial releases retains exactly the fees a single full release would have. A fiscal invoice is issued per released slice rather than once at the end, which keeps VAT reporting correct on a long-running contract. Passreference on each payout request and it appears on that period’s invoice.
Webhooks
Subscribe to these to keep your own records in step:escrow.release_request.created
A payout request was created and the amount reserved. The client has been notified.
escrow.release_request.approved
The client approved the period.
escrow.release_request.auto_approved
The review window elapsed with no response, so the period was approved automatically.
escrow.release_request.disputed
The client rejected the period. A scoped dispute is open; the amount stays reserved.
escrow.release_request.resolved
DHMAD resolved a disputed period.
awardedTnd is what went to the seller.escrow.release_request.released
Funds reached the seller’s DHMAD balance for this period.
escrow.balance.exhausted
No unreserved balance remains. Pause tracking and prompt a top-up; the escrow stays open.
escrow.balance.exhausted, escrow.status.updated, escrow.updated, escrow.funds.added / refunded) includes the live face + TND snapshot (availableForRelease*, reserved*, releasedTotal*, plus fundedAmount* / remainingAmount* / refundedTotal* when progressive). Release-request events also carry amountFace, currency, and fxRateUsed on data.releaseRequest.
Prefer Get Escrow when reconciling after a skinny delivery — it always returns the full progressive snapshot.
Identity requirements
The seller must have completed identity verification before any payout can be requested. The check runs at request creation, not at payout, so you get an immediateSELLER_IDENTITY_REQUIRED error instead of periods silently accumulating as unpayable.
Because the check is idempotent, it passes for every future period once the freelancer is verified and linked. Onboarding them to that state is your platform’s responsibility — prompt for it before the first billing period, not weekly.
Progressive release composes with
mode: "quick" (no contract) or mode: "standard" (with a signed contract). It is not available for instant mode, which completes on payment.