How to exercise the Seller API safely before production: sandbox magic SKUs, mocks, contract tests, and webhook replay.
What this covers#
Sandbox fixtures, what to assert in CI, and how to verify webhooks without guessing signatures. Start from Sandbox if you still need a key.
Prerequisites#
- Sandbox API key (
environment = sandbox) - Ability to receive HTTPS webhooks (public HTTPS endpoint or temporary tunnel)
- Stable
Idempotency-Keyvalues in automated suites
Confirm the key#
Call GET /whoami first. Assert environment, scopes, and capabilities before any order test.
curl -s -X GET "https://api.avrix.io/api/seller/v1/whoami" \
-H "Authorization: Bearer $AVRIX_API_KEY"Sandbox magic SKUs#
The shared sandbox catalog includes deterministic SKUs for happy and negative paths:
| SKU | Use for |
|---|---|
SANDBOX-ALWAYS-001 | Happy path preview → order → fulfilment (large replenished pool) |
SANDBOX-STD-001 | Standard happy-path fulfilment (finite pool — prefer ALWAYS for CI) |
SANDBOX-EUR1-001 | Cheap PSP dogfood (~€1); countryCode DE/FR/NL, expectedUnitPriceCents 100 |
SANDBOX-NOKEYS-001 | Out of stock — preview canFulfill: false, order 409 NO_AVAILABLE_KEYS |
SANDBOX-TERR-BLOCK-001 | Territory block (DE-only price list; use countryCode US) |
SANDBOX-PRICE-MIS-001 | Wrong expectedUnitPriceCents → 422 CATALOG_PRICE_MISMATCH |
SANDBOX-WHOLESALE-MIS-001 | Wholesale grant at 2500¢ — any other bind → WHOLESALE_PRICE_MISMATCH |
SANDBOX-NOALLOC-001 | Share without allocation — sellable: false, order fails entitlement |
SANDBOX-NOSHARE-001 | No seller share — not in partner catalog |
SANDBOX-LIFECYCLE-001 | Product not live → PRODUCT_NOT_SELLABLE |
SANDBOX-PREORDER-001 | Visible, sellable: false; commit → PRODUCT_NOT_SELLABLE |
SANDBOX-LOWSTOCK-001 | Pool pinned at 3 keys — qty 1 succeeds, qty 4 → 409 |
SANDBOX-KEYLESS-001 | Hosted redemption when whoami.capabilities.keylessEnabled is on |
SANDBOX-CHURN-001 | Live ↔ archived every 6 hours — poll GET /catalog/changes |
Prefer SANDBOX-ALWAYS-001 for golden-path CI. For live PSP test charges, prefer
SANDBOX-EUR1-001 so the catalog/bind price is €1.00 — still use your
processor’s test mode; do not charge production cards against sandbox orders.
Production store scenarios#
A store-scenario matrix walks every integration profile and magic SKU against
Production (https://api.avrix.io) with a sandbox key. It covers:
- Profile A —
GET /whoami,GET /partners,GET /integration/status, catalog browse,GET /products/{id}forSANDBOX-ALWAYS-001, allocations (sellable: true), preview gate,POST /orders, idempotent replay,orderReferencereplay - Profile B — sellable catalog sync, availability, reconcile
(
GET /orders?orderReference=, transactions, events), finance ledger (GET /finance/api-sales,GET /finance/summary), webhook test ping plus HMAC fixture verify, optional checkout hold, one refund plusGET /refunds, andPOST /keys/return - Profile C — hot-drop reserve/commit, or 412
HOT_DROP_NOT_ENABLEDas a valid fallback to Profile B - Magic SKU gates — the table above, including extra-platform browse
(
SANDBOX-ALWAYS-PS5-001) - Multi-SKU cart —
POST /orders/bulkwithstrict: true(happy path and a failing line that returns 422VALIDATION_FAILEDwith no ALWAYS fulfillment) - Recoveries — 409 after a simulated PSP charge (refund guidance) and
same
orderReference+ higher quantity → 409ORDER_REFERENCE_CONFLICT(never top up) - Live-key isolation — a production-environment key must not see or pull sandbox magic SKUs
- Exceptional paths — auth and the error envelope, catalog freshness
(
If-None-Match304, checksum,since=), missingIdempotency-Key, price/country/currency validation, unknown-outcome replay, hold and hot-drop faults, refund lookup and compensated replay after a full refund, bulk contract-date defaults, webhook signature rejects, and sandbox simulate headers (rate_limit,hot_path_timeout,key_decrypt_failed,billing_record_failed,internal_error,idempotency_in_flight) that return the live codes without pulling keys
Exceptional store paths#
A store must handle more than the happy path. Against sandbox, rehearse:
- Auth and envelope — missing or garbage Bearer (
UNAUTHORIZED),KEY_ENVIRONMENT_MISMATCH,UNSUPPORTED_API_VERSION, missingIdempotency-Key(BAD_REQUEST). Every error includescode,requestId,recoverable, anddoc_url. - Validation —
SALES_COUNTRY_REQUIRED,EXPECTED_UNIT_PRICE_REQUIRED,CURRENCY_MISMATCH,quantity: 0, empty or unknownskuCode. - Catalog freshness —
If-None-Match304,GET /catalog/checksum,GET /catalog/changes?since=. - Idempotency — same key + different body →
IDEMPOTENCY_KEY_MISMATCH; sameorderReference+ higher quantity →ORDER_REFERENCE_CONFLICTbefore you refund. After a full refund the same reference may pull again (compensated replay). - Unknown outcome — abort the HTTP call, then replay the same
Idempotency-Keyand look upGET /orders?orderReference=. - Hold / hot-drop —
HOLD_NOT_FOUND,HOLD_MISMATCH,CHECKOUT_HOLD_DISABLED,HOT_DROP_NOT_ENABLED,RESERVATION_GONE. - Refunds —
GET /refunds/{refundId}, unknownkeyIds, no plaintext onGET /orders. - Bulk contract dates — omit
strictand pinX-Avrix-Api-Version: 2026-09-01(whole cart) vs2026-04-29(partial). - Webhooks — reject stale and missing signatures; reject non-HTTPS and loopback URLs.
- Simulate headers (sandbox only) — the modes in Sandbox return the live codes and pull nothing. Retry the same key without the header.
Ops and recovery#
After the first charge, rehearse the store-ops paths — not more commerce gates.
- Webhook consumer — dedupe on
X-Avrix-Event-Id(same signed body twice is still 200). AfterPATCH /webhooks/{id}withrotateSecret: true, accept both the new secret and the previous one during the overlap window.GET /webhooksandGET /webhooks/{id}/deliveriesafter/testare the ops reads. - Delayed delivery —
X-Avrix-Sandbox-Simulate: webhook_delay_30sonPOST /ordersstill returns 200 and the order is immediately visible onGET /orders?orderReference=. Buffer byorderReferenceuntil the signed event arrives; do not wait on the HTTP response to persist keys. - Ledger sweep — page
GET /orders?from=&to=&cursor=(there is noupdatedSince). The Profile AorderReferenceappears; responses never include plaintext keys. - Price bind — when preview returns
priceCommitmentToken, echo it onPOST /orders. A tampered token is 422PRICE_COMMITMENT_INVALID. If preview omits the token, skip — the deployment is not issuing binds. - Hold / hot-drop depth — replay the same hold
Idempotency-Key; commit afterDELETE /orders/holdstill fulfils and reportscheckoutHold.committedWithoutHold: true. ConflictingexpectedUnitPriceCents/expectedWholesaleUnitPriceCentsisEXPECTED_PRICE_CONFLICT(orVALIDATION_FAILED). One successfulPOST /orders/{reservationId}/extend, thenRESERVATION_EXTEND_EXHAUSTED. Concurrent commits yield one fulfilment orRESERVATION_COMMIT_IN_PROGRESS. Simulatehot_path_timeouton commit (not onlyPOST /orders) — nothing fulfilled. - Price drift — after
CATALOG_PRICE_MISMATCH, re-preview and submit the new cents. Do not retry the old price.
From the Avrix repository, with AVRIX_API_KEY set to a sandbox key:
# Production (sandbox key required; mutation-capped)
AVRIX_API_KEY=avrix_sk_sbx_your_key_here \
node scripts/run-seller-store-scenarios.mjs --base-url https://api.avrix.io --sandbox --no-seed
# Same matrix against a running local app
AVRIX_API_KEY=avrix_sk_sbx_your_key_here \
node scripts/run-seller-store-scenarios.mjs --sandbox --no-seed
SANDBOX-EUR1-001 (DE preview/order at €1.00), SANDBOX-TERR-BLOCK-001
(US → territory rejection), and SANDBOX-PREORDER-001
(PRODUCT_NOT_SELLABLE) are asserted as contract codes. Production sandbox
picks up those catalog pins on the next sandbox catalog refresh. The suite
never seeds inventory, never load-tests Production, and refuses a
production-environment key.
What to mock vs call live#
| Layer | Recommendation |
|---|---|
| Your storefront UI | Mock your own BFF; never call Avrix from the browser |
| Unit tests for mappers | Mock HTTP; assert request shape and error mapping |
| Contract / integration | Hit sandbox with magic SKUs |
| Webhook handlers | Replay signed fixtures; verify HMAC on raw body |
Contract tests#
Minimum assertions against sandbox:
GET /whoami— sandbox environment and required scopes.- Catalog sync — snapshot + allocations merge yields sellable SKUs.
POST /orders/preview—canFulfilltrue forSANDBOX-ALWAYS-001.POST /orders— sameIdempotency-Keyreplays the same order.- Webhook — signature verifies; handler is idempotent.
Webhook verification in tests#
Verify HMAC over the raw body before parsing JSON.
# Verification is server-side in your webhook handler — not a curl call.
# Header: X-Avrix-Signature: t=<unix>,v1=<hex>
# Signed payload: "{t}.{rawBody}" with HMAC-SHA256 and your endpoint secret.Replay recorded deliveries (or console “resend”) against your webhook endpoint. Handlers must tolerate duplicates — see Webhooks and Polling and reconciliation.
Common mistakes#
- Asserting plaintext keys on
GET /orders(they are never returned there). - Running production keys in CI “because sandbox was empty”.
- Treating a single mocked
200as coverage of 409 / territory failures.
Next steps#
- Certification — required evidence before go-live
- Troubleshooting — symptom-first triage
- Error reference — codes and recovery