Skip to main content
This page is the agent prompt that complements the V1 → V2 migration reference. Drop a coding agent into your integration repo, point it at this URL, and the agent ports your code phase-by-phase with human checkpoints. Want the human-readable port reference instead? Start here.

Invoking the agent

Claude Code is the reference implementation. From your integration repo:
cd your-zinc-integration-repo
claude

# Then in the Claude prompt:
> Read https://docs.zinc.com/v2/migration-agent-runbook and execute the v1 → v2 migration on this repository.
Other agents work too — the runbook is plain text and language-agnostic. Just paste the full content of this page as the prompt.

Prime directives

  1. Never silently drop functionality. Anything without a 1-to-1 v2 mapping gets a flag in MIGRATION_GAPS.md and a TODO(zinc-migration) comment at the call site.
  2. Commit per phase. Each numbered phase below is a separate commit, so the human can review small diffs.
  3. Surface uncertainty. If you can’t tell whether a string is a product ID or a URL, whether max_price is in cents or dollars, whether a status branch should map to order_placed or in_progress — flag it instead of guessing.
  4. Do not invent credentials or secrets. API keys, webhook secrets, retailer credentials, and the v2 base URL come from the human via env vars or the dashboard.
  5. The live docs are the source of truth. Where this runbook and the live docs disagree, the live docs win — v2 evolves and your training data may be stale.

Webhook Wire Reference

v2 delivers all events to a single webhook URL configured per account in the dashboard. The agent must not invent this URL, the secret, or the header names — they are fixed by the API and reproduced here so Phase 6 does not depend on fetching live docs.

Delivery

  • Transport: HTTP POST to the account’s configured webhook_url.
  • Content-Type: application/json
  • Headers on every delivery:
    • X-Webhook-Event — the event name (e.g. order.failed)
    • X-Webhook-Signature — hex-encoded HMAC-SHA256 of the raw request body

Signature verification

The signature is HMAC-SHA256(secret, raw_body), hex-encoded. The secret is the account’s webhook signing secret (prefix zn_whsec_), obtained from the dashboard — never generated by the agent. Compute the HMAC over the exact bytes received, before any JSON re-serialization, and compare timing-safely.
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

Payload envelope

{
  "event": "order.failed",
  "order_id": "<uuid>",
  "return_id": null,
  "status": "<current status>",
  "timestamp": "<ISO 8601>",
  "data": {}
}
  • return_id is populated only on return.* events; it is null for order events.
  • status is the order status for order.* events and the return status for return.* events.
  • data carries event-specific fields. For order.failed it includes error_type and error.

Complete event list

Order lifecycle: order.started, order.placed, order.failed, order.tracking_received, order.delivered, order.cancelled Return lifecycle: return.created, return.approved, return.denied, return.credited When mapping v1 event names in Phase 6, map only to names in this list. Any v1 event with no equivalent here goes to MIGRATION_GAPS.md rather than a guessed name.

Phase 0 — Ground in the current docs

Before writing any code, fetch the live v2 documentation. Do not rely on what you remember about the Zinc API; v2 ships changes frequently. Fetch at minimum:
  • https://www.zinc.com/docs/llms.txt — the full documentation index
  • https://www.zinc.com/docs/versions/latest.json — the OpenAPI spec, the source of truth for field names and types
  • This page and the migration reference
  • Endpoint pages for any endpoints this codebase uses. At minimum:
    • https://www.zinc.com/docs/v2/api-reference/introduction/authentication.md
    • https://www.zinc.com/docs/v2/api-reference/orders/create-order.md
    • https://www.zinc.com/docs/v2/api-reference/orders/get-order.md
    • https://www.zinc.com/docs/v2/api-reference/orders/cancel-order.md
    • https://www.zinc.com/docs/v2/api-reference/introduction/webhooks.md
    • https://www.zinc.com/docs/v2/api-reference/introduction/error-handling.md
    • https://www.zinc.com/docs/v2/api-reference/introduction/idempotency.md
    • https://www.zinc.com/docs/v2/api-reference/introduction/sandbox.md
    • https://www.zinc.com/docs/v2/api-reference/returns/create-return.md (plus get-return, list-returns)
    • https://www.zinc.com/docs/v2/api-reference/products/get-product.md, get-product-offers.md, search.md
If the codebase uses managed accounts, MPP, or cross-retailer search, read those pages too — they’re linked from the index.

Phase 1 — Repo setup

Read-only confirmation. Stop and ask the human if any of these are unclear:
  • Identify the repo’s language(s) (Python / Node / Ruby / Go / PHP / etc.) and HTTP client patterns (requests, axios, fetch, raw curl, an SDK).
  • Identify the existing test harness — you’ll run it as validation in Phase 8.
  • Create a new branch zinc-v2-migration.

Phase 2 — Discovery → MIGRATION_INVENTORY.md

Search the repo for every v1 touchpoint. Inventory includes code paths, config, and persistence layer — not just HTTP call sites. Code patterns:
  • URL strings containing api.zinc.io (any path)
  • URL strings containing /v0/, /v1/, or /v2/ near an HTTP call
  • Headers / variables named client_token, ZINC_CLIENT_TOKEN, zinc_token, Authorization: Basic …
  • Imports of any v1 SDK: zinc, @zinc/api, zinc-api, zincapi, etc.
  • request_id polling loops — v1 returned {"request_id": "..."} from order creation and required polling GET /v1/orders/{request_id}; while in flight, that endpoint returned the request_processing error code rather than a real status
  • Webhook handler routes whose payloads or paths reference v1 event names: order_placed, order_failed, tracking_obtained, tracking_updated, status_updated, case_updated, return_placed, return_failed, request_succeeded, request_failed
v1 request fields to grep for (many have no v2 equivalent and need flagging): retailer, product_id, seller_selection_criteria, shipping_method, retailer_credentials, payment_method, billing_address, is_gift, gift_message, client_notes, addax, webhooks (v1 accepted per-request webhook URLs in the order body), max_price v1 error / status codes to grep for: _type: "error", request_processing, aborted_request, invalid_login, max_price_exceeded Persistence:
  • Database columns or models storing request_id, v1 status strings (e.g. pending, submitted, placed), or v1 error codes — these may need a schema migration to hold v2’s UUID order IDs and the new status enum.
  • Migration shape: keep v1 columns readable during the transition window (existing in-flight v1 orders shouldn’t break); add v2 columns alongside.
Config / secrets:
  • Environment variables, .env* files, secret-manager templates, CI configs holding Zinc client tokens.
Configuration:
  • Any per-environment config (Stripe-style keys, Slack channels, etc.) that points to Zinc — note which environments exist so the agent doesn’t accidentally mix test and prod keys later.
Write MIGRATION_INVENTORY.md at the repo root:
# Zinc v1 → v2 Migration Inventory

Generated by Claude Code on <ISO date>. Do not edit by hand — re-run
discovery to refresh.

## API call sites

| File | Line | v1 endpoint | Phase | Status |
|------|------|-------------|-------|--------|
| src/orders.py | 42 | POST /v1/orders | 4 | pending |
| src/returns.py | 17 | POST /v1/returns | 4 | pending |

## Webhook handlers

| File | Line | v1 event(s) handled | Status |
|------|------|---------------------|--------|
| src/webhooks/order_placed.py | 5 | order_placed | pending |

## v1 credentials in code (potential leak — review)

| File | Line | Pattern matched |
|------|------|-----------------|
| config/.env.example | 3 | ZINC_CLIENT_TOKEN=… |
Commit: chore(zinc): inventory v1 call sites for v2 migration. If the inventory contains zero rows, stop — there are no v1 calls to migrate; surface that to the human.

Phase 3 — Auth and base URL

One commit, repo-wide:
1

Add or rename env vars

Store the key in the customer’s environment under a local name such as ZINC_API_KEY (the zn_live_* / zn_test_* bearer token) and add ZINC_BASE_URL (the v2 production host; default to a placeholder and flag for human confirmation in MIGRATION_PREREQS.md). These env var names are a convention for the customer’s repo, not values the API defines. The key itself is minted in the dashboard, never invented by the agent.
2

Remove the old auth config

Strip ZINC_CLIENT_TOKEN and any basic-auth password vars from .env.example, config.{py,js,...}, CI configs, and secret-manager templates. Add a deprecation comment.
3

Rewrite every HTTP-client construction site

Authorization: Bearer ${ZINC_API_KEY} instead of Authorization: Basic <base64(token)>.
4

Strip URL prefixes

Drop /v0, /v1, /v2 — v2 has no version prefix.
Do not rewrite request bodies in this phase. Endpoint paths beyond the prefix change still match in URL structure for most resources; bodies move in Phase 5.
Two keys, never mixed. Sandbox and production share the same base URL — there is no separate sandbox host. Provision a zn_test_* key for dev / staging / CI and a zn_live_* key for production only; the key prefix alone selects the environment (zn_test_* routes to the sandbox, zn_live_* to production). Add a config-time assertion that prevents zn_live_* from ever loading in non-prod environments, and vice-versa — and use timing-safe comparison for webhook signatures.
Commit: feat(zinc): switch to v2 bearer auth and base URL.

Phase 4 — Pre-flight checklist → MIGRATION_PREREQS.md

Write MIGRATION_PREREQS.md with the human-required steps from the pre-flight checklist in the reference. Tailor it to what discovery found.
# Zinc v2 Migration — Manual Pre-flight

The agent has scaffolded the code migration. Before any v2 calls will
succeed, you (a human) need to:

- [ ] Sign in to the v2 dashboard at https://app.zinc.com and create
      an account via Stytch magic-link.
- [ ] Confirm the v2 production base URL and set ZINC_BASE_URL.
- [ ] Mint a `zn_live_*` API key and set ZINC_API_KEY.
- [ ] Mint a `zn_test_*` API key for CI and set ZINC_TEST_API_KEY.
- [ ] Top up your v2 wallet. The agent counted N order placements in
      your repo; estimate budget accordingly.
- [ ] Register your webhook URL via POST /users. The agent identified
      these existing v1 webhook URLs — pick one or consolidate:
        - https://yours.example.com/wh/placed
        - https://yours.example.com/wh/tracking
- [ ] Copy the account's webhook signing secret (prefix `zn_whsec_`)
      from the dashboard into the customer's environment (e.g.
      ZINC_WEBHOOK_SECRET). This is the HMAC key for verifying
      X-Webhook-Signature. It is account-scoped and dashboard-issued —
      the agent must not generate it.
- [ ] Create managed accounts for these (retailer, email) pairs the
      agent found in your code:
        - amazon / buyer1@example.com
        - amazon / buyer2@example.com
        - walmart / buyer1@example.com
- [ ] Record the returned `short_id` for each; set them in config
      (the agent left placeholders: ZINC_MANAGED_ACCOUNT_AMAZON_BUYER1, …).
Commit: docs(zinc): manual pre-flight checklist for v2 migration.

Phase 5 — Endpoint-by-endpoint port

For each row in MIGRATION_INVENTORY.md, apply the transformation from the endpoint mapping and request shape diffs in the reference. Group by resource and commit per resource:
  • feat(zinc): port order placement to v2 /orders
  • feat(zinc): port returns to v2 /returns
  • feat(zinc): port retailer credentials to managed-accounts model
  • feat(zinc): port tracking reads to embedded order tracking

Per-call-site checklist

For each call site:
1

URL change

Apply the mapping from the reference’s endpoint mapping. If the mapping is no equivalent:
  • Leave the calling code intact.
  • Add # TODO(zinc-migration): no v2 equivalent for {endpoint} — see MIGRATION_GAPS.md.
  • Append a row to MIGRATION_GAPS.md. Continue.
2

Request body transformation

Apply the field-by-field rules from Request shape diffs.
3

Response body transformation

Apply Response shape diffs everywhere downstream code reads the response. Search the codebase for accesses to request_id, merchant_order_ids, _status, price_components, etc., and rewrite.
4

Status string branches

If you find if status == "placed" or similar, rewrite to the v2 enum value ("order_placed"). Centralise into a helper if there are >3 sites.
5

Update the inventory row

Set status to done and link the commit SHA.

Hard rules

If you find a literal email/password/TOTP in the codebase that was previously passed to v1’s retailer_credentials, replace it with a config reference (e.g. config.ZINC_MANAGED_ACCOUNT_<NAME>) and add a row to MIGRATION_PREREQS.md. Do not move secrets between files.
v2 requires integer cents. If the customer’s code multiplies or divides by 100 before sending to v1, that math probably stays. If it doesn’t, flag the call site for the human to confirm.
If a product_id is being used and you can’t confidently identify the retailer (e.g. from a sibling retailer: "amazon" argument), flag it.
idempotency_key is still supported on v2 /orders; pass it through. Drop it for endpoints where v1 had it and v2 doesn’t (returns).

Phase 6 — Webhook handlers

1

Find every v1 webhook handler route

Typically one route per event type.
2

Consolidate into a single handler

Match the existing URL the customer is most likely to register in v2 (the most common destination in the v1 webhooks objects).
3

Insert HMAC-SHA256 signature verification

Implement signature verification per the Webhook Wire Reference above. Use ZINC_WEBHOOK_SECRET as the HMAC key, compute over the raw request body, and reject with 401 if the X-Webhook-Signature header doesn’t match. Use hmac.compare_digest (or the language equivalent) to avoid timing leaks.
4

Map v1 event names to v2 event names

Use the event name mapping. If the customer’s handler relied on status_updated / case_updated / request_succeeded / request_failed / return_failed, add TODO(zinc-migration) + a MIGRATION_GAPS.md row.
5

Delete or comment out per-event handlers

Now merged into the dispatcher.
Commit: feat(zinc): consolidate webhook handlers for v2 single-URL model.

Phase 7 — Gaps review → MIGRATION_GAPS.md

By the end of Phase 6 you should have a populated MIGRATION_GAPS.md. Ensure each row has:
  • File and line of the original v1 call
  • The v1 endpoint or event involved
  • A one-sentence description of what the customer was doing with it
  • Your suggested workaround if any (e.g., “store merchant_order_id locally and look it up; v2 does not expose insert_merchant_order_id”), or none — discuss with Zinc if you have no good suggestion
Commit: docs(zinc): document migration gaps for human review.
Stop the migration here and surface MIGRATION_GAPS.md to the human. Do not proceed to Phase 8 until the human has acknowledged the gaps file — otherwise validation will run against a half-migrated integration and lie about success.

Phase 8 — Validation

Drive scenarios with the dedicated test product URLs rather than placing real orders. Fetch the live catalog from GET /orders/test-products and exercise at minimum:
Test productWhat it exercises
https://zinc.com/shop/products/test-successFull happy path through order_placed, tracking numbers, and price components
https://zinc.com/shop/products/test-invalid-addressSynchronous failure — rejected at order-creation time
https://zinc.com/shop/products/test-url-unreachableSynchronous failure
https://zinc.com/shop/products/test-insufficient-fundsSynchronous failure
https://zinc.com/shop/products/test-out-of-stockAsynchronous failure — order creates successfully, then fails during processing and arrives as an order.failed webhook
https://zinc.com/shop/products/test-price-exceededAsynchronous failure
https://zinc.com/shop/products/test-invalid-variantAsynchronous failure
https://zinc.com/shop/products/test-shipping-unavailableAsynchronous failure
The sync-vs-async failure split is the most common code path you’ll get wrong. Some errors are returned by the API immediately (4xx with a code/message); others come back asynchronously as order.failed webhooks after the order was successfully created. The application must handle both — code that only inspects the create-order response will silently drop the async failures.
1

Confirm pre-flight is complete

All checkboxes in MIGRATION_PREREQS.md are ticked, env vars are set in .env.test or equivalent.
2

Run the customer's test suite against the v2 sandbox

Use ZINC_TEST_API_KEY and ZINC_BASE_URL. Sandbox is the same base URL as production — the zn_test_* key prefix alone determines test mode.
3

Exercise the full lifecycle with the test products

Create → receive webhooks → tracking on test-success → every synchronous and asynchronous failure scenario → (if used) cancel and return flows. Confirm webhook signature verification passes on real sandbox deliveries, not just unit-test fixtures.
4

Tail webhook-receiver logs during the test run

Confirm signature verification passes and event names map correctly.
5

Write MIGRATION_REPORT.md

Summary including:
  • Number of call sites migrated
  • Number of gap rows requiring human decisions
  • Test suite pass/fail counts
  • Outstanding TODOs
Sandbox limitations — test mode skips wallet balance, URL reachability, and address validation checks so you can test without funding real wallets or hitting real retailers. Plan a small set of real production orders as the final cutover check before declaring the migration done.
Commit: docs(zinc): migration report.

What to do when stuck

Trust the reference (it’s generated against v2’s live OpenAPI schema). File an issue against the reference page with the specific discrepancy.
Add a row to MIGRATION_GAPS.md with endpoint not documented in migration guide. Do not guess.
Consider whether the customer has an internal SDK wrapper you can edit once instead of N times. Look in lib/, clients/, services/zinc* before mass-editing.
Stop and report — don’t try to fix unrelated test infrastructure.

Output files summary

By the end of the migration, the customer’s repo has these new top-level files:
FilePurpose
MIGRATION_INVENTORY.mdEvery v1 call site, status per phase
MIGRATION_PREREQS.mdHuman checklist for dashboard / wallet / credentials setup
MIGRATION_GAPS.mdEndpoints with no v2 equivalent + suggested workarounds
MIGRATION_REPORT.mdFinal summary
And the code is ported on a branch named zinc-v2-migration, ready for the customer’s normal PR review process.