Disputes & Negotiations
When buyer and seller disagree, the escrow freezes and both parties enter a structured negotiation. No funds move until the dispute resolves — by agreement, admin decision, or timeout.
When a Dispute Arises
A dispute can be raised by either party at any point while the escrow is active and not yet completed or cancelled. The most common triggers are:
GRN Mismatch (automatic)
When the buyer submits a Goods Received Note and the item quantities or prices do not match the seller's delivery note, the system automatically transitions to DISPUTED.
Manual Dispute
Either party can manually raise a dispute — for example, if goods arrived damaged, the wrong item was delivered, or the buyer disputes the quality of work on a service contract.
You cannot raise a dispute on an escrow that is already COMPLETED or CANCELLED. Disputes must be raised while the escrow is still active.
Raising a Dispute
Post to POST /v1/escrow/:id/dispute with the actor raising the dispute and a reason. The escrow status immediately moves to DISPUTED and the underlying lock transaction is also marked disputed.
| Field | Type | Description |
|---|---|---|
| actor | EscrowActor enum | The party raising the dispute: BUYER, SELLER, or SYSTEM |
| reason | string | A human-readable explanation of why the dispute is being raised |
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/dispute" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"actor": "BUYER",
"reason": "Received 8 units but invoice shows 10. Delivery note claims 10 were shipped."
}'{
"status": "DISPUTED",
"disputeId": "dsp_01hx..."
}This endpoint is idempotent. If the escrow is already DISPUTED, it returns the existing settlement rather than creating a duplicate event.
The Negotiation Flow
Once disputed, either party can propose a revised split plan. A negotiation proposal specifies a new breakdown of seller payout, system fee, and service fee. The total can be reduced (triggering a partial refund to the buyer), but it can never exceed the original locked amount.
Propose
Either party posts a new split plan. No money moves. The proposal sits as PENDING.
Review
The other party sees the proposal. They can accept, reject, or counter with their own proposal.
Accept
On acceptance, the new split takes effect. If the new total is less, the difference refunds immediately to the buyer.
Release
After acceptance commits, finalizeRelease runs automatically to distribute the agreed amounts.
A party cannot accept their own proposal. If you submitted the negotiation, the other party must be the one to accept. The only exception is the SYSTEM actor, which can accept any proposal.
Proposing a New Split
Use POST /v1/escrow/:id/negotiate to submit a revised split plan. Include an optional notes field to communicate the reasoning to the other party.
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/negotiate" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"actor": "BUYER",
"notes": "Willing to pay for the 8 units actually received. Proposing 80% of original amount.",
"newSplitPlan": {
"seller_payout_cents": 352800,
"system_fee_cents": 3600,
"service_fee_cents": 3600
}
}'| Field | Type | Required | Notes |
|---|---|---|---|
| actor | BUYER | SELLER | Yes | The party proposing the new split. |
| newSplitPlan | EscrowSplitPlanDto | Yes | The proposed split. Must sum to less than or equal to the original total amount. |
| notes | string | No | Optional message to explain the counter-offer. |
{
"negotiationId": "neg_03kz...",
"status": "PENDING",
"action": "NEGOTIATION_PROPOSAL",
"proposedBy": "BUYER",
"newSplitPlan": {
"seller_payout_cents": 352800,
"system_fee_cents": 3600,
"service_fee_cents": 3600
},
"notes": "Willing to pay for the 8 units actually received. Proposing 80% of original amount.",
"createdAt": "2026-06-23T11:45:00.000Z"
}Accepting a Proposal
The counterparty accepts by calling POST /v1/escrow/:id/negotiate/:negId/accept. If the accepted total is less than the original, the difference is immediately refunded to the buyer before release runs.
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/negotiate/neg_03kz/accept" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"actor": "SELLER"
}'| Field | Type | Required | Notes |
|---|---|---|---|
| actor | BUYER | SELLER | Yes | The counterparty accepting the proposal. Cannot be the one who proposed it. |
{
"negotiationId": "neg_03kz...",
"status": "ACCEPTED"
}Rejecting a Proposal
Reject a proposal with POST /v1/escrow/:id/negotiate/:negId/reject. No money moves on rejection. Either party can then submit a new counter-proposal.
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/negotiate/neg_03kz/reject" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"actor": "SELLER"
}'{
"negotiationId": "neg_03kz...",
"status": "REJECTED"
}Resolution Paths
Negotiated Agreement
Both parties reach a mutually accepted split. Funds release immediately on acceptance. Escrow moves to COMPLETED.
Admin Escalation
Either party can request admin intervention. An admin calls POST /v1/escrow/:id/admin-resolve with a binding split plan.
Timeout (Milestone zero-payout)
Milestone adjustment proposals that propose zero payout are auto-accepted by the system after 32 hours if still unresolved.
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/admin-resolve" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"adminId": "admin_uuid",
"newSplitPlan": {
"seller_payout_cents": 390000,
"system_fee_cents": 4500,
"service_fee_cents": 4500
}
}'| Field | Type | Required | Notes |
|---|---|---|---|
| adminId | string | Yes | ID of the admin making the decision. |
| newSplitPlan | EscrowSplitPlanDto | Yes | The final imposed split plan. Must not exceed the original total amount. |
{
"status": "RESOLVED",
"finalSplit": {
"seller_payout_cents": 390000,
"system_fee_cents": 4500,
"service_fee_cents": 4500
}
}Admin resolve is only permitted when the escrow status is DISPUTED. The admin-specified total cannot exceed the original locked amount.
Milestone Adjustment Disputes
On milestone escrows, disputes can be scoped to a single milestone rather than the whole escrow. Use POST /v1/escrow/:id/milestones/:milestoneId/adjust to raise an adjustment proposal. Proposing zero cents signals a full cancellation of that milestone.
curl -X POST "https://api.switchlock.co.zw/v1/escrow/esc_02jy/milestones/ms_03/adjust" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-company-id: YOUR_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"actor": "BUYER",
"proposedSellerPayoutCents": 180000,
"reason": "Frontend integration is only 60% complete. Proposing partial payment for work done."
}'| Field | Type | Required | Notes |
|---|---|---|---|
| actor | BUYER | SELLER | Yes | The party proposing the new payout. |
| proposedSellerPayoutCents | integer | Yes | The proposed payout for the seller. Set to 0 to cancel the milestone entirely. |
| reason | string | No | Optional reason for the adjustment. |
{
"negotiationId": "neg_04wx...",
"status": "PENDING"
}Setting proposedSellerPayoutCents to 0 cancels the milestone entirely on acceptance. The buyer is refunded the full milestone amount.