Inspiration

While researching how payment gateways handle post-capture risk, one thing became obvious very quickly: the system is heavily biased toward buyers and penalizes honest sellers.

When a merchant captures a payment on PayPal, they face two major threats to their cash flow:

  1. First-Party Friendly Fraud: According to the LexisNexis True Cost of Fraud Study and Chargebacks911 benchmarks, 61% to 71% of all e-commerce chargebacks are friendly fraud, where a legitimate buyer receives their item and still files an ITEM_NOT_RECEIVED or UNAUTHORISED claim. Every $1.00 of friendly fraud costs the seller $4.61 once you factor in $15 to $20 dispute fees, operational overhead, and lost inventory. Worse, merchants leave 43% of claims uncontested and lose 78% of the ones they fight manually. Why? Because PayPal gives buyers 180 days to open a dispute, while couriers like FedEx, UPS, and DHL purge public tracking histories and GPS Proof of Delivery (POD) scans after 90 to 120 days (unless the seller pays extra for extended archiving). If a buyer opens a claim on Day 135, the carrier link is already dead.
  2. The 21-Day Volume-Spike Freeze: According to Pitney Bowes and ShipBob fulfillment benchmarks, 82% to 91% of warehouse shipping labels are printed within 4 hours of checkout in 3PL systems like ShipStation or Delhivery, but over 64% of mid-market stores only sync those tracking numbers back to the payment processor on a 24-to-48-hour nightly cron job. If a store has a viral product launch and daily GMV jumps 4.6x (say, from $4,000 to $18,450), PayPal's risk model only sees the payment rail. It sees a massive velocity spike and unshipped orders, assumes "bust-out scam" risk, and locks the seller's cash in a 21-day rolling reserve right when they need to pay suppliers.

We built ZeroHold (merchant-aegis) to give sellers a post-capture defense workbench that archives delivery proofs before they expire, runs relational Visa Compelling Evidence 3.0 (CE 3.0) checks automatically, and reconciles warehouse waybills and wholesale invoices before an automated reserve hold can strike.


What It Does

ZeroHold operates after payment capture through two core engines:

  • Engine A (Relational Visa CE 3.0 & Carrier POD Vault): At capture and Day-4 delivery, ZeroHold hashes the buyer's checkout IP, device fingerprint, shipping zip, and live carrier GPS delivery coordinates into a tamper-evident SHA-256 vault. When a CUSTOMER.DISPUTE.CREATED webhook arrives, it routes the defense based on the dispute reason:
  • For UNAUTHORISED claims, it runs a relational SQL query across a 120-to-365-day buyer_history_ledger to prove the buyer has at least 2 prior undisputed PayPal orders matching at least 2 of 4 core identifiers (with IP address or device fingerprint mandatory). If a buyer has fewer than 2 valid historical orders (or if a prior order was disputed), the system refuses to fake compliance and escalates the row for manual review.
  • For MERCHANDISE_OR_SERVICE_NOT_RECEIVED (INR) claims, where the buyer admits making the purchase but claims non-delivery, it bypasses CE 3.0 and auto-defends using the archived Day-4 carrier waybill and GPS delivery scan.

  • Engine B (Unbacked Capture Exposure & Attestation Workbench): Continuously monitors 24-hour GMV velocity and unshipped capture backlog. Instead of acting as a one-click "magic green button", it stages pending 3PL warehouse manifests directly inside an inline-editable AG Grid workbench. The merchant reviews or edits the carrier, tracking waybill, and wholesale distributor Purchase Order (PO) ID inside the grid, signs an inventory attestation, and syncs the batch live to api-m.sandbox.paypal.com, generating a printable Resolution Center Underwriting Packet for human risk reviewers.


How We Built It

We built ZeroHold as a deployable Python and FastAPI web application backed by SQLite in Write-Ahead Logging (WAL) mode, integrating APIMatic and AG Grid at the core of the workflow:

  1. APIMatic PayPal Python Server SDK (v2.4.0) & Context Plugin: We installed the APIMatic PayPal Context Plugin (npx context-plugins install paypal) into .context-plugins/paypal/ to map out the exact controller schemas and model contracts. In paypal_service.py, we initialized PaypalServersdkClient with ClientCredentialsAuthCredentials in Environment.SANDBOX. Every core lifecycle step runs through APIMatic's typed controllers:
  2. TransactionSearchController.search_transactions polls 24-hour transaction velocity windows (/v1/reporting/transactions).
  3. OrdersController.create_order mints live 17-character orders on PayPal Sandbox (/v2/checkout/orders) using OrderRequest, PurchaseUnitRequest, and AmountWithBreakdown.
  4. OrdersController.patch_order and OrdersController.get_order apply RFC 6902 Patch operations (PatchOp.REPLACE) to persist carrier waybills, wholesale supplier invoice IDs, and CE 3.0 defense stamps directly onto PayPal's cloud servers, capturing the Paypal-Debug-Id trace header on every call.

  5. AG Grid (v31.3.2) Interactive Operations Workbench: Rather than treating AG Grid like a glorified HTML table, we used it as the primary data-entry and triage engine:

  6. Inline Cell Editors (singleClickEdit: true): Columns for Wholesale PO, Carrier, and Waybill use agTextCellEditor and agSelectCellEditor (FEDEX, DHL, UPS, BLUEDART) so ops teams can stage 3PL manifests and edit tracking data directly in the grid.

  7. Floating Column Filters & Pinned Totals: Every key column uses agTextColumnFilter or agNumberColumnFilter (floatingFilter: true), while pinnedBottomRowData aggregates live 24h GMV, protected volume percentage, and unbacked capture counts.

  8. Live Server Verification Modal: Clicking Verify on PayPal inside any row fires OrdersController.get_order against api-m.sandbox.paypal.com and displays the raw JSON stored on PayPal's backend.

  9. Mathematical Risk Model: To quantify reserve exposure without leaking secret underwriting thresholds, we compute the 24-hour GMV velocity Z-score (Z) against the merchant's 30-day rolling baseline (μ = $4,000/day, σ = $620):

$$Z = \max\left(0, \frac{\text{GMV} - \mu}{\sigma}\right)$$

We then calculate the composite Unbacked Capture & Reserve Exposure Score (from 4.0 to 99.0) combining the velocity anomaly (Z), the unbacked capture ratio (U / N, where U is untracked orders and N is total orders), the attestation factor (γ), and proximity to the 1.50% (0.015) Visa VAMP / PayPal dispute ratio cliff (R):

$$\text{ExposureScore} = \min(3.8 \cdot Z, 42.0) \cdot \gamma + 52.0 \cdot \left(\frac{U}{N}\right) + \min\left(14.0 \cdot \frac{R}{0.015}, 14.0\right)$$

Here, γ = 0.15 only after the merchant completes the human-in-the-loop waybill and supplier invoice attestation and PayPal's Sandbox server confirms HTTP 200 OK across all synced orders (and γ = 1.0 otherwise). During a 4.6x flash sale (Z = +23.31σ with 5 of 7 orders unshipped), exposure jumps to 87.1 / 100. Once the 3PL waybills and distributor POs are attested and synced to PayPal, exposure drops to 14.3 / 100.


Challenges We Ran Into

Building this forced us to confront several architectural and domain challenges where our initial prototype assumptions fell apart:

  1. Killing the "Prototype Mindset" & Preventing Threshold Surfing: In our early iterations, clicking a "Defuse Freeze" button automatically generated fake tracking numbers and turned the dashboard green. Stepping back, we realized two problems with that approach: first, a backend script cannot magically know a merchant's wholesale supplier invoices without ERP or seller input; second, if a dashboard acts as a "ban predictor", bust-out scammers could use it to threshold-surf right below 95% and cash out before getting frozen. We redesigned Engine B around Unbacked Capture Exposure: over 60% of the risk score is tied to physical unshipped backlog. A scammer with no inventory cannot lower their score because doing so requires live carrier waybills and signed wholesale distributor POs through a human-in-the-loop attestation gate.
  2. PayPal Sandbox Headless Order State & HTTP 403 / 404 Traps: When you create an order via POST /v2/checkout/orders in a headless backend, it sits in CREATED status because no human buyer opened a browser popup to log into a Sandbox buyer account and click "Pay Now". Consequently, calling PayPal's legacy /v1/shipping/trackers-batch endpoint returned HTTP 403 Forbidden, and unapproved Sandbox orders expired after PayPal's 3-hour TTL, causing subsequent PATCH calls to fail with HTTP 404 RESOURCE_NOT_FOUND while our local UI still showed AUTO-DEFENDED. We fixed this by:
  3. Using OrdersController.patch_order (PATCH /v2/checkout/orders/{id}) to persist our PROTECTED waybill/PO stamps and DEFENDED CE 3.0 proofs directly into purchase_units[0].custom_id and description on PayPal's servers.
  4. Building an automatic TTL re-anchoring layer in paypal_service.py that detects HTTP 404 on expired Sandbox orders, mints a fresh order on the fly, updates the foreign key in SQLite, and retries the patch.
  5. Strictly gating all local SQLite status updates on HTTP 200 OK from PayPal so a failed API call can never produce a false green badge in the UI.

  6. State Ordering & AG Grid Layout Bugs: At one point, our dashboard showed exposure dropping from 87.1 to 14.3, while the printable PDF report showed 87.1 -> 50.0. Tracing the execution order in sentinel.py revealed we were computing the report's risk score before inserting the attestation dossier into SQLite, so the $\gamma_{\text{attested}} = 0.15$ multiplier wasn't active yet when the PDF JSON was serialized. On the frontend, enabling wrapText: true and autoHeight: true in AG Grid caused cells to inherit a 42px line-height, bloating two-line rows to 84px until we overrode .ag-cell-wrap-text with a compact 1.35 line-height.


What We Learned

  • How Visa CE 3.0 & Dispute Routing Actually Work: We learned why UNAUTHORISED and ITEM_NOT_RECEIVED disputes require completely different data structures. A carrier delivery scan on a porch does not defeat an UNAUTHORISED (stolen card) claim because a fraudster can ship to a drop address; defeating it requires a relational join over 120-to-365-day transaction history matching IP address or device fingerprint. Conversely, a buyer who fails CE 3.0 because of a prior dispute can still be auto-defended against an ITEM_NOT_RECEIVED claim if you archived the carrier's GPS POD before their 90-day log purge.
  • Read-After-Write Verification on Payment APIs: Trusting local database state in a fintech app is dangerous. Adding a GET /v2/checkout/orders/{id} read-after-write verification step after every PATCH (and exposing that raw response to the user via the Verify on PayPal button) caught bugs we would have otherwise missed.
  • Designing Human-in-the-Loop AI & Compliance Systems: Automation works best when it handles the heavy data-stitching (like joining 365 days of buyer fingerprints or staging 3PL manifests into AG Grid) while keeping a human officer in the loop for legal attestations like wholesale inventory verification.

Built With

Share this project:

Updates

Submission history