Chargeback & Dispute Proof-of-Delivery Tracking API (Stripe, Shopify, PayPal & Escrow)
When a cardholder files an "Item Not Received" (INR) chargeback (Visa Reason Code 13.1, Mastercard 4855, American Express C08) or a PayPal / Shopify Payments dispute, card networks give merchants a strict window to submit compelling delivery evidence. A screenshot of an internal ERP status or an unverified tracking link rarely wins—issuing banks look for a complete, chronological carrier audit trail showing:
- Carrier name and tracking number (
carrier,trackingNumber,service). - Terminal delivery confirmation (
status: "delivered",deliveredAtUTC timestamp,deliveredAtLocalwall-clock time, andsignedBywhen available). - Destination city/state/country alignment (
destination) matching the cardholder's shipping address. - Full chronological checkpoint history (
events[]) from origin acceptance through local sortation,out_for_delivery, and terminaldeliveredscan.
lastscanned gives dispute-automation platforms, Shopify brands, 3PLs, and B2B escrow systems a deterministic, normalized JSON audit trail across 542 carriers—via synchronous GET /v1/track/:number, batch GET /v1/track?numbers=..., or Watches & Webhooks (managed in the Web UI Dashboard or via POST /v1/watches).
For one-off manual disputes, you can also generate formatted evidence packets in your browser with the free Chargeback & Delivery Dispute Evidence Packet Generator.
Why Dispute Automation Fails Without lastscanned
| Dispute Evidence Challenge | What Happens With Standard Carrier Portals / Legacy Aggregators | How lastscanned Solves It |
|---|---|---|
| Carrier purges tracking history before the 120-day chargeback window | Cardholders can file INR disputes up to 120 days after purchase, when many regional and postal carriers have already recycled or hidden web tracking pages. | Create a Watch at fulfillment (POST /v1/watches or in app.lastscanned.com/watches). When the package transitions to delivered, your webhook receives and archives the complete immutable result payload immediately. |
| Cross-border parcels show "Delivered to local courier" instead of buyer's city | Querying Yanwen (UL494089685YP), 4PX (4PX3002639757438CN), Cainiao (LP00836938654367), or YunExpress (YT2621200705470145) alone often stops at customs handover, losing the dispute. | lastscanned extracts the domestic last-mile carrier and barcode in message ("Handed to UniUni as UUS5BM1269395824723"), letting you attach both the international leg and the local doorstep delivery scan (UniUni, SpeedX, GOFO, OnTrac, USPS). |
Regional couriers (UniUni, SpeedX, Veho, GOFO, OnTrac) lack Stripe auto-populate | Stripe and Shopify Payments only auto-populate basic tracking for major legacy carriers, leaving regional deliveries blank in automated responses. | Fetch the normalized TrackingResult from GET /v1/track/:number and format the complete events[] log directly into Stripe's shipping_documentation / uncategorized_text fields. |
1. Building a Stripe dispute.created Automated Evidence Handler
When Stripe sends a charge.dispute.created webhook with reason === "product_not_received", your backend can query GET https://api.lastscanned.com/v1/track/{trackingNumber} synchronously, resolve any cross-border last-mile handoff, and attach the complete carrier checkpoint log to the Stripe dispute in seconds:
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function attachTrackingEvidenceToStripeDispute(
disputeId: string,
trackingNumber: string,
shippingAddress: string,
customerName: string,
) {
// 1. Fetch live normalized tracking history from lastscanned
const res = await fetch(
`https://api.lastscanned.com/v1/track/${encodeURIComponent(trackingNumber)}`,
{ headers: { Authorization: `Bearer ${process.env.LASTSCANNED_API_KEY}` } },
);
if (!res.ok) throw new Error(`Tracking lookup failed: ${res.status}`);
const pkg = await res.json();
// 2. If cross-border parcel handed off to a domestic last-mile courier, fetch the doorstep leg too
let lastMilePkg = null;
const handoffMatch = pkg.message?.match(/Handed to .+ as ([A-Z0-9]{8,34})/i);
if (handoffMatch) {
const lmRes = await fetch(
`https://api.lastscanned.com/v1/track/${encodeURIComponent(handoffMatch[1])}`,
{ headers: { Authorization: `Bearer ${process.env.LASTSCANNED_API_KEY}` } },
);
if (lmRes.ok) lastMilePkg = await lmRes.json();
}
const activePkg = lastMilePkg ?? pkg;
const timelineLines = activePkg.events.map(
(e: { timestamp: string | null; localTime: string | null; status: string; location: string | null; description: string }) =>
`${e.timestamp ?? e.localTime ?? "N/A"} | [${e.status.toUpperCase()}] ${e.location ?? "Carrier Network"} — ${e.description}`,
);
const evidenceSummary = [
`CARRIER PROOF OF DELIVERY AUDIT LOG`,
`Primary Tracking Number: ${pkg.trackingNumber} (${pkg.carrier.toUpperCase()})`,
lastMilePkg ? `Final-Mile Tracking Number: ${lastMilePkg.trackingNumber} (${lastMilePkg.carrier.toUpperCase()})` : null,
`Terminal Status: ${activePkg.status.toUpperCase()} (${activePkg.statusDescription ?? ""})`,
`Delivered Timestamp (UTC): ${activePkg.deliveredAt ?? "See scan log"}`,
`Delivered Timestamp (Local): ${activePkg.deliveredAtLocal ?? "See scan log"}`,
activePkg.signedBy ? `Signed By: ${activePkg.signedBy}` : null,
`Chronological Carrier Scans (${activePkg.events.length} checkpoints):`,
...timelineLines,
]
.filter(Boolean)
.join("\n");
// 3. Submit structured fields + chronological checkpoint log to Stripe Dispute API
await stripe.disputes.update(disputeId, {
evidence: {
customer_name: customerName,
shipping_address: shippingAddress,
shipping_carrier: activePkg.carrier.toUpperCase(),
shipping_tracking_number: activePkg.trackingNumber,
shipping_date: activePkg.events.at(-1)?.localTime?.slice(0, 10) ?? undefined,
uncategorized_text: evidenceSummary,
},
submit: false, // Set true to auto-submit when activePkg.status === "delivered"
});
}2. Archiving Delivery Proof Automatically with Watches (app.lastscanned.com/watches & POST /v1/watches)
Because carriers periodically purge tracking records after 45–90 days—while cardholders have up to 120 days to initiate a chargeback—the safest practice is to archive the terminal delivered payload the day the package arrives:
- Via the Web UI Dashboard (
app.lastscanned.com/watches): Paste up to 100 tracking numbers at a time in the browser, monitor Active vs. Ended (Delivered) shipments, and configure your webhook endpoint on Webhooks (app.lastscanned.com/webhooks). - Via
POST /v1/watches: Register each tracking number when an order ships. When the carrier records the final delivery scan,lastscannedsends a signedtracking.updatedwebhook withresult.status === "delivered",result.deliveredAt,result.signedBy, and the completeresult.eventsarray so you can store an immutable copy alongside the order record in your database.
Related Guides & Interactive Tools
LTL Freight & Heavy Parcel Tracking API (Estes, Saia, XPO, R+L, ABF, TForce, Central Transport, FedEx Freight & More)
Track 18+ LTL, pallet, and freight carriers (Estes, Saia, XPO LTL, R+L Carriers, ABF Freight, TForce, FedEx Freight, Dayton Freight, Pitt Ohio, SEFL, Central Transport, Roadrunner, Forward Air, Day & Ross, CEVA, DSV, Kuehne+Nagel, Mainfreight) via one synchronous REST API, MCP, and Web UI Dashboard Watches.
Carrier API Errors & Scan Status Reference
Fix common carrier tracking API errors (UPS 403/428/250003, USPS Web Tools retirement, FedEx 422/OAuth, Veho App Check, 17TRACK -18019902 / -18010020) and decode 75 cryptic carrier scan statuses.