Sandbox → Production Migration

This guide walks an integration from a sandbox tenant to a production tenant. Read it before your go-live date — the single most common launch-day incident is an integration that still carries a sandbox credential or a sandbox-era reference ID.

How environments work in VerityPay

VerityPay does not have a Stripe-style test/live mode switch. There are no sk_test_ / sk_live_ token prefixes and no per-request environment header. Instead, isolation comes from two layers:

  1. Tenants. Every tenant is a fully isolated dataset (its own database schema). A "sandbox" is simply a separate tenant — typically a demo organization provisioned for you — with its own contacts, transactions, tokens, and webhook endpoints. Nothing is shared with your production tenant.
  2. Deployments. Staging and production run as separate deployments with separate databases and separate signing keys. A staging host is never a window into production data.

The practical consequence: the token is the environment. Whatever token you send resolves to exactly one tenant in exactly one deployment (see Authentication). There is nothing else to configure per request.

Property Sandbox Production
Base URL Your staging host /api/v1 https://app.veritypay.com/api/v1
Tenant Demo/sandbox tenant Your live client tenant
API token Minted in the sandbox tenant Minted in the production tenant
Dataset Isolated, disposable Isolated, real
Reference IDs Environment-specific Environment-specific (different!)
Webhook endpoints Registered per tenant Must be re-registered
Payments Provider sandboxes — no real money Live payment rails — real money
API contract Identical (/api/v1, same envelope) Identical

Migration checklist

Work through these in order. Every item is a hard requirement, not a suggestion.

1. Mint a fresh production token

Sandbox tokens cannot authenticate in production — the token resolves to its own tenant, and that tenant does not exist in the production deployment. Attempting it returns 401 unauthenticated.

In your production tenant dashboard, open Settings → API Tokens → New Token and mint one token per service (e.g., billing-service-prod). Grant it the same abilities you validated in sandbox — diff the two ability lists explicitly; a missing ability surfaces later as a surprise 403 forbidden. See Authentication for the catalog and recommended bundles.

Store the new token in your secrets manager under a distinct key (VERITYPAY_TOKEN_PROD, not a reused VERITYPAY_TOKEN). Keeping sandbox and production credentials in separately named slots is the cheapest defence against a config mix-up.

2. Update the base URL

Point your client at https://app.veritypay.com/api/v1. The path, versioning, headers, error envelope, and pagination are byte-for-byte identical to sandbox — only the host and credentials change. Pin both host and token in deploy-time configuration, never resolved at runtime from shared state.

3. Do not carry reference IDs across

Reference IDs (tnt_…, cnt_…, and every other prefixed public ID) are derived per environment — the same underlying record would have a different public ID in each deployment, and sandbox records don't exist in production at all. Any sandbox ID stored in your database, queue messages, or config is garbage after cutover.

Instead:

  • Re-create or re-import the entities you need (contacts, custom-field definitions) via the production API.
  • Re-resolve by natural key. Phone numbers (E.164) are the canonical identity for contacts on this platform — look entities up fresh rather than replaying stored IDs.
  • Audit your persistence layer for columns holding VerityPay IDs and make sure they're populated from production responses, not migrated from sandbox rows.

4. Re-register webhook endpoints

Webhook endpoints, their signing secrets, and their delivery history are all tenant-scoped. In the production tenant:

  1. Register each endpoint again (POST /api/v1/webhook-endpoints) with your production receiver URL.
  2. Capture the new whsec_… signing secret — it is revealed once at creation, exactly like sandbox.
  3. Store it in your secrets manager under a production-specific key and verify signatures with it per the Webhooks guide.

Your verification code doesn't change; only the secret does. A receiver still verifying with the sandbox secret will silently reject every production delivery.

5. Expect real money

In sandbox, payment rails point at provider sandboxes — nothing settles. In production, payables disburse real funds and receivables charge real customers. Before enabling any automated write path:

  • Re-run your smoke tests with small, real amounts you're prepared to settle.
  • Confirm Idempotency-Key is sent on every write — duplicate payables are annoying in sandbox and expensive in production.
  • Confirm your 429 backoff and webhook retry handling one more time; production traffic patterns differ from test scripts.

6. Run the go-live smoke test

With the production token in place, verify the full loop end-to-end:

GET /api/v1/contacts HTTP/1.1
Host: app.veritypay.com
Authorization: Bearer vp_pat_••••••••••••••••••••
Accept: application/json
  1. Auth check — the request above returns 200 (not 401/403).
  2. Ability check — hit one endpoint per ability you granted; each returns 2xx, none 403.
  3. Write check — create a test contact with an Idempotency-Key, then replay the identical request and confirm you get the cached response, not a duplicate.
  4. Webhook check — trigger an event and confirm your receiver verifies the signature with the production whsec_ secret and returns 2xx.
  5. Teardown — delete the test contact (soft-delete; restore exists if you need it back).

Common cutover failures

Symptom Cause
401 unauthenticated on every call Sandbox token sent to production. Mint a production token (§1).
403 forbidden on some calls Production token missing an ability the sandbox token had. Diff them.
404 resource_not_found on stored IDs Sandbox reference IDs replayed against production (§3).
Webhook deliveries failing verification Receiver still using the sandbox whsec_ secret (§4).
Webhook endpoint auto-disabled 50 consecutive failed deliveries — fix the receiver, then re-enable it.
Duplicate payables after a network retry Missing Idempotency-Key on writes. Never ship without it (§5).

Keep sandbox after launch

Don't decommission your sandbox tenant at go-live. Keep it (and its distinctly named credentials) for:

  • rehearsing API version upgrades (/api/v1/api/v2) before touching production,
  • validating new abilities and webhook event types as they ship, and
  • reproducing support cases without touching live customer data.

When you contact api-support@veritypay.com about a cutover issue, include the request_id from the failing response and state explicitly which environment it came from.