SwitchLock Docs
Escrow / Disputes

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.

FieldTypeDescription
actorEscrowActor enumThe party raising the dispute: BUYER, SELLER, or SYSTEM
reasonstringA 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."
  }'
Response — 201 Created
{
  "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.

1

Propose

Either party posts a new split plan. No money moves. The proposal sits as PENDING.

2

Review

The other party sees the proposal. They can accept, reject, or counter with their own proposal.

3

Accept

On acceptance, the new split takes effect. If the new total is less, the difference refunds immediately to the buyer.

4

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
    }
  }'
FieldTypeRequiredNotes
actorBUYER | SELLERYesThe party proposing the new split.
newSplitPlanEscrowSplitPlanDtoYesThe proposed split. Must sum to less than or equal to the original total amount.
notesstringNoOptional message to explain the counter-offer.
Response — negotiation created
{
  "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"
  }'
FieldTypeRequiredNotes
actorBUYER | SELLERYesThe counterparty accepting the proposal. Cannot be the one who proposed it.
Response — 200 OK
{
  "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"
  }'
Response — 200 OK
{
  "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
    }
  }'
FieldTypeRequiredNotes
adminIdstringYesID of the admin making the decision.
newSplitPlanEscrowSplitPlanDtoYesThe final imposed split plan. Must not exceed the original total amount.
Response — 200 OK
{
  "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."
  }'
FieldTypeRequiredNotes
actorBUYER | SELLERYesThe party proposing the new payout.
proposedSellerPayoutCentsintegerYesThe proposed payout for the seller. Set to 0 to cancel the milestone entirely.
reasonstringNoOptional reason for the adjustment.
Response — negotiation created
{
  "negotiationId": "neg_04wx...",
  "status": "PENDING"
}

Setting proposedSellerPayoutCents to 0 cancels the milestone entirely on acceptance. The buyer is refunded the full milestone amount.

Next Steps