Skip to main content
Progressive release lets you fund one escrow and pay it out in slices over time — one payout per billing period. It is the primitive behind hourly and time-and-materials contracts, retainers, and any arrangement where the total is known but the work arrives incrementally. DHMAD deliberately owns only the money mechanics. Your platform owns the hours, the timesheets, and when a period closes.

How it differs from a normal escrow

A normal escrow releases all-or-nothing: the buyer funds it, then complete pays out the whole amount and the escrow ends. A progressive escrow instead:
  • stays in paid status across many payouts,
  • decrements its balance on each release,
  • accepts top-ups with add_funds at any time,
  • and only reaches completed when 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_release checkout session) — the slice is released to the seller’s DHMAD balance immediately.
  • Reject (reject_release checkout 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 approvalDeadline passes.
DHMAD notifies the client on creation and again before the deadline.
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

Live amount / 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

On completed / cancelled (including escrow.status.updated and Get Escrow):
  • availableForRelease* = 0, reserved* = 0, remaining* = 0
  • releasedTotal* = what was actually paid to the seller
  • refundedTotal* = unused balance returned
You do not need to hard-settle float locally when those fields are present.

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. Pass reference 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.
Every progressive balance payload (release-request events, 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 immediate SELLER_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.
Design your weekly job to be at-least-once. requestId is the idempotency key: a repeated call returns the existing request with 200 rather than reserving twice.
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.