# Chargeback & Dispute Proof-of-Delivery Tracking API (Stripe, Shopify, PayPal & Escrow)

URL: https://docs.lastscanned.com/chargeback-dispute-proof-of-delivery-tracking-api

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:

1. **Carrier name and tracking number** (`carrier`, `trackingNumber`, `service`).
2. **Terminal delivery confirmation** (`status: "delivered"`, `deliveredAt` UTC timestamp, `deliveredAtLocal` wall-clock time, and `signedBy` when available).
3. **Destination city/state/country alignment** (`destination`) matching the cardholder's shipping address.
4. **Full chronological checkpoint history** (`events[]`) from origin acceptance through local sortation, `out_for_delivery`, and terminal `delivered` scan.

**[lastscanned](https://lastscanned.com)** gives dispute-automation platforms, Shopify brands, 3PLs, and B2B escrow systems a deterministic, normalized JSON audit trail across **[542 carriers](/carriers)**—via synchronous `GET /v1/track/:number`, batch `GET /v1/track?numbers=...`, or **[Watches & Webhooks](/webhooks)** (managed in the **[Web UI Dashboard](https://app.lastscanned.com/watches)** 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](https://lastscanned.com/tools/chargeback-delivery-proof-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`](https://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:

```ts
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:

1. **Via the Web UI Dashboard ([`app.lastscanned.com/watches`](https://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`)](https://app.lastscanned.com/webhooks)**.
2. **Via `POST /v1/watches`**: Register each tracking number when an order ships. When the carrier records the final delivery scan, `lastscanned` sends a signed `tracking.updated` webhook with `result.status === "delivered"`, `result.deliveredAt`, `result.signedBy`, and the complete `result.events` array so you can store an immutable copy alongside the order record in your database.

---

## Related Guides & Interactive Tools

- **[Interactive Chargeback & Delivery Dispute Evidence Packet Generator](https://lastscanned.com/tools/chargeback-delivery-proof-generator)**
- **[Watches and Webhooks (`app.lastscanned.com/watches` & `POST /v1/watches`)](/webhooks)**
- **[Cross-Border & Last-Mile Handoff Tracking Guide (`Yanwen`, `4PX`, `Cainiao`, `YunExpress`)](/cross-border-last-mile-handoff-tracking-yanwen-4px-cainiao)**
- **[Shopify, 3PL & Custom ERP Package Tracking & Webhooks Guide](/shopify-3pl-wismo-tracking-and-webhooks)**
