Buyer Protection Service Low-Level Design: Claim Filing, Escrow Hold, and Resolution Workflow

What Is a Buyer Protection Service?

A buyer protection service gives buyers recourse when a transaction goes wrong — item not received, item significantly not as described, or unauthorized charge. It must determine claim eligibility quickly, place a hold on funds while the case is open, collect and evaluate evidence from both parties, run automated resolution logic, and escalate to human agents when the case is ambiguous. The design must enforce a strict state machine, prevent double-claims on a single order, and integrate with the payments layer for escrow and disbursement.

Requirements

Functional

  • Check whether a buyer is eligible to file a claim against a given order based on order status, delivery confirmation, and time window.
  • Place an automated escrow hold on seller funds when a valid claim is opened.
  • Collect evidence from buyer (photos, messages) and seller (tracking info, delivery proof) via structured uploads.
  • Run automated resolution rules to close low-complexity cases without human review.
  • Escalate unresolved cases to a human disputes team after a configurable review window.

Non-Functional

  • Eligibility check under 200 ms at P99.
  • Escrow hold placed within five seconds of claim creation.
  • State transitions are idempotent to handle duplicate webhook deliveries from the payments system.

Data Model

  • Claim: claim_id, order_id, buyer_id, seller_id, reason (NOT_RECEIVED, NOT_AS_DESCRIBED, UNAUTHORIZED), status (ELIGIBILITY_CHECK, OPEN, EVIDENCE_REQUESTED, UNDER_REVIEW, AUTO_RESOLVED, ESCALATED, CLOSED), resolution (FULL_REFUND, PARTIAL_REFUND, DENIED, null), escrow_hold_id (nullable), opened_at, deadline_at, closed_at.
  • EligibilityResult: result_id, claim_id, eligible (bool), denial_reason (nullable), order_status_snapshot, evaluated_at.
  • Evidence: evidence_id, claim_id, submitted_by (BUYER, SELLER), evidence_type (IMAGE, TRACKING_NUMBER, MESSAGE_THREAD, DELIVERY_SIGNATURE), storage_key (nullable), text_value (nullable), submitted_at.
  • EscrowHold: hold_id, claim_id, amount_cents, currency, payment_provider_reference, status (ACTIVE, RELEASED_TO_BUYER, RELEASED_TO_SELLER, PARTIAL_RELEASE), created_at, released_at.
  • ResolutionDecision: decision_id, claim_id, decided_by (SYSTEM, AGENT), rule_applied (nullable), agent_id (nullable), outcome, justification, decided_at.

Core Algorithms

Claim Eligibility Check

The eligibility engine fetches the order record and applies rules in sequence. The order must be in COMPLETED or DELIVERED status. The filing window must not have expired (default: 30 days from delivery confirmation for NOT_RECEIVED, 14 days for NOT_AS_DESCRIBED). No existing open or closed claim may exist for the same order and reason. The order must have a non-zero seller payment that has cleared. Each rule returns PASS or FAIL with a reason code. The first FAIL short-circuits evaluation. The result is written to EligibilityResult and returned synchronously. If eligible, claim creation proceeds in the same request.

Automated Escrow Hold

Immediately after a OPEN claim is persisted, the service calls the payments provider API to place a hold on the seller disbursement balance equal to the order amount (or the claimed amount for partial claims). The payment provider returns a hold reference. The EscrowHold row is created with status ACTIVE. If the payments call fails, the claim is marked OPEN but flagged for retry by a background job that polls for claims with no active hold and retries every 60 seconds up to five times before alerting the disputes operations team.

Resolution State Machine

Valid transitions: OPEN -> EVIDENCE_REQUESTED (system sends evidence collection prompts to both parties), EVIDENCE_REQUESTED -> UNDER_REVIEW (evidence deadline passes or both parties submit), UNDER_REVIEW -> AUTO_RESOLVED (automated rules produce a confident decision), UNDER_REVIEW -> ESCALATED (no confident decision within review window), ESCALATED -> CLOSED (human agent decides), AUTO_RESOLVED -> CLOSED (no appeal within 48 hours). Automated rules evaluate: tracking shows non-delivery past SLA (AUTO FULL_REFUND), seller submits valid delivery signature matching buyer address (AUTO DENIED), high-value order with ambiguous evidence (ESCALATE). Each rule is a configuration-driven entry with a condition expression and an outcome. On AUTO_RESOLVED a ResolutionDecision row is written with the rule_applied field populated.

Scalability and Reliability

  • Idempotent state transitions: Each transition checks the current status before writing. A duplicate event attempting to re-open an already OPEN claim returns the existing claim_id without side effects. The EscrowHold creation uses the claim_id as an idempotency key with the payments provider.
  • Evidence storage: Evidence files (images, signed delivery documents) are stored in object storage with a 90-day retention policy aligned to the maximum dispute window. Presigned download URLs are generated on demand rather than stored, preventing link sharing beyond the resolution workflow.
  • Deadline enforcement: A scheduled job runs every minute and queries for claims where deadline_at is past and status is EVIDENCE_REQUESTED, moving them to UNDER_REVIEW automatically. The same job moves stale UNDER_REVIEW claims to ESCALATED when the review window expires.
  • Escrow release atomicity: Fund release (to buyer on REFUND, to seller on DENIED) is initiated via an outbox pattern: the release intent is written to the database in the same transaction as the status update, and a worker processes the payments API call separately. This prevents a state where funds are released but the claim status is not updated, or vice versa.

API Design

  • POST /claims/eligibility — check claim eligibility for an order without creating a claim; returns eligible bool and denial reason.
  • POST /claims — create a claim; triggers eligibility check, escrow hold, and evidence request notifications.
  • GET /claims/{id} — fetch claim state, escrow hold status, evidence submitted, and resolution detail.
  • POST /claims/{id}/evidence — submit evidence; accepts multipart form with evidence_type and file or text.
  • POST /claims/{id}/escalate — buyer or seller requests human review before the automatic escalation deadline.
  • POST /claims/{id}/resolution — agent endpoint to set outcome with justification; triggers escrow release.
  • GET /claims?order_id={id} — fetch all claims associated with an order for dispute history display.

Key Design Decisions

Separating the eligibility check into its own endpoint allows the frontend to inform buyers upfront whether a claim can be filed before collecting any details, reducing abandoned claim flows. Using the outbox pattern for escrow release rather than calling the payments API inline on the status update decouples resolution correctness from payments provider availability. Storing resolution rules in configuration rather than code allows the disputes policy team to tune automated decision thresholds in response to fraud pattern changes without engineering deployments.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How is claim eligibility determined in a buyer protection system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Eligibility rules are evaluated against the order record: the purchase must be within the protection window (e.g., 180 days), the payment method must be eligible, the item category must be covered, and no prior claim may exist for the same order. These rules run as a policy engine check when a buyer initiates a claim, returning an eligibility decision with a reason code.”
}
},
{
“@type”: “Question”,
“name”: “How does automated escrow hold work in buyer protection?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “When a qualifying claim is opened, the payment processor's API is called to place a hold on any pending payout to the seller. If funds have already been settled, a reserve or clawback is initiated. The hold persists until the claim is resolved. On a buyer-favorable outcome the held amount is refunded; on a seller-favorable outcome the hold is released.”
}
},
{
“@type”: “Question”,
“name”: “What evidence is collected during a buyer protection claim?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The system collects: buyer-submitted photos or screenshots, order metadata (tracking number, delivery confirmation, item description), seller responses and shipping carrier data pulled via API, and communication logs between buyer and seller. All evidence is stored in object storage with immutable references tied to the claim record for audit purposes.”
}
},
{
“@type”: “Question”,
“name”: “How is the resolution state machine designed for buyer protection claims?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “States typically include: OPENED -> EVIDENCE_COLLECTION -> UNDER_REVIEW -> (ESCALATED | AUTO_RESOLVED) -> CLOSED. Transitions are triggered by events (evidence submitted, SLA timer expired, agent decision). Each transition is persisted as an immutable event in an audit log. Terminal states (CLOSED_REFUNDED, CLOSED_DENIED, CLOSED_WITHDRAWN) trigger payment actions and notification events.”
}
}
]
}

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Shopify Interview Guide

See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems

Scroll to Top