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:
- 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_RECEIVEDorUNAUTHORISEDclaim. 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. - 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.CREATEDwebhook arrives, it routes the defense based on the dispute reason: - For
UNAUTHORISEDclaims, it runs a relational SQL query across a 120-to-365-daybuyer_history_ledgerto 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:
- 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. Inpaypal_service.py, we initializedPaypalServersdkClientwithClientCredentialsAuthCredentialsinEnvironment.SANDBOX. Every core lifecycle step runs through APIMatic's typed controllers: TransactionSearchController.search_transactionspolls 24-hour transaction velocity windows (/v1/reporting/transactions).OrdersController.create_ordermints live 17-character orders on PayPal Sandbox (/v2/checkout/orders) usingOrderRequest,PurchaseUnitRequest, andAmountWithBreakdown.OrdersController.patch_orderandOrdersController.get_orderapply RFC 6902Patchoperations (PatchOp.REPLACE) to persist carrier waybills, wholesale supplier invoice IDs, and CE 3.0 defense stamps directly onto PayPal's cloud servers, capturing thePaypal-Debug-Idtrace header on every call.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:Inline Cell Editors (
singleClickEdit: true): Columns forWholesale PO,Carrier, andWaybilluseagTextCellEditorandagSelectCellEditor(FEDEX,DHL,UPS,BLUEDART) so ops teams can stage 3PL manifests and edit tracking data directly in the grid.Floating Column Filters & Pinned Totals: Every key column uses
agTextColumnFilteroragNumberColumnFilter(floatingFilter: true), whilepinnedBottomRowDataaggregates live 24h GMV, protected volume percentage, and unbacked capture counts.Live Server Verification Modal: Clicking
Verify on PayPalinside any row firesOrdersController.get_orderagainstapi-m.sandbox.paypal.comand displays the raw JSON stored on PayPal's backend.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:
- 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.
- PayPal Sandbox Headless Order State &
HTTP 403 / 404Traps: When you create an order viaPOST /v2/checkout/ordersin a headless backend, it sits inCREATEDstatus 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-batchendpoint returnedHTTP 403 Forbidden, and unapproved Sandbox orders expired after PayPal's 3-hour TTL, causing subsequentPATCHcalls to fail withHTTP 404 RESOURCE_NOT_FOUNDwhile our local UI still showedAUTO-DEFENDED. We fixed this by: - Using
OrdersController.patch_order(PATCH /v2/checkout/orders/{id}) to persist ourPROTECTEDwaybill/PO stamps andDEFENDEDCE 3.0 proofs directly intopurchase_units[0].custom_idanddescriptionon PayPal's servers. - Building an automatic TTL re-anchoring layer in
paypal_service.pythat detectsHTTP 404on expired Sandbox orders, mints a fresh order on the fly, updates the foreign key in SQLite, and retries the patch. Strictly gating all local SQLite status updates on
HTTP 200 OKfrom PayPal so a failed API call can never produce a false green badge in the UI.State Ordering & AG Grid Layout Bugs: At one point, our dashboard showed exposure dropping from
87.1to14.3, while the printable PDF report showed87.1 -> 50.0. Tracing the execution order insentinel.pyrevealed 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, enablingwrapText: trueandautoHeight: truein AG Grid caused cells to inherit a42pxline-height, bloating two-line rows to84pxuntil we overrode.ag-cell-wrap-textwith a compact1.35line-height.
What We Learned
- How Visa CE 3.0 & Dispute Routing Actually Work: We learned why
UNAUTHORISEDandITEM_NOT_RECEIVEDdisputes require completely different data structures. A carrier delivery scan on a porch does not defeat anUNAUTHORISED(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 anITEM_NOT_RECEIVEDclaim 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 everyPATCH(and exposing that raw response to the user via theVerify on PayPalbutton) 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
- ag-grid
- apimatic
- context-plugins
- fastapi
- httpx
- javascript
- oauth2
- paypal
- paypal-server-sdk
- pydantic
- python
- rest-api
- sql
- sqlite
- tailwindcss
- uvicorn
- webhooks