Production readiness checklist

Eight concrete things worth verifying before real merchant traffic hits your integration — each one with the exact code or behavior to check, not just a reminder to 'be careful.'

None of these are hypothetical. Each maps to a specific, real failure mode described elsewhere in the docs — this lesson exists to turn that scattered knowledge into one list you can actually check off before a launch, not to introduce new behavior.

1

Idempotency-Key on every billed POST

Both scan and whatsapp/send should send a fresh Idempotency-Key on every call. Grep your codebase for both endpoint paths and confirm neither one is missing the header — a single unretried code path here is how double-charges happen in production, not in testing.

// Every POST to scan or whatsapp/send should include this header.
// Search your codebase for these two paths and confirm both set it:
fetch('https://askbiz.co/api/v1/scan', {
  headers: { 'Idempotency-Key': crypto.randomUUID() /* ← must be present */ },
})
fetch('https://askbiz.co/api/v1/whatsapp/send', {
  headers: { 'Idempotency-Key': crypto.randomUUID() /* ← must be present */ },
})
2

Rate-limit headers drive backoff, not a hardcoded number

Plan limits can change (an upgrade, a temporary adjustment). Read X-RateLimit-Remaining and X-RateLimit-Limit from the actual response instead of hardcoding free=5/min, growth=60/min, business=120/min in your own logic — those numbers are documented for planning, not for you to encode as constants.

// Read the two rate-limit headers on every response instead of hardcoding
// your plan's limit — they reflect your account's real, current plan.
const res = await fetch('https://askbiz.co/api/v1/ask', { headers, body })
const remaining = Number(res.headers.get('X-RateLimit-Remaining'))
const limit = Number(res.headers.get('X-RateLimit-Limit'))
if (remaining < limit * 0.1) {
  // back off or alert — you're close to a 429
}
3

Webhook signatures are actually verified

If you consume sale.created, purchase_order.received, or stock.low, confirm you verify the x-askbiz-signature HMAC against the raw request body with a constant-time comparison — not a plain === that leaks timing information, and not skipped entirely because "it worked in testing."

import crypto from 'node:crypto'

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  // Constant-time compare — never use === on secrets/signatures
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}
4

Every non-2xx status is handled, not just caught generically

402, 403, and 429 each carry specific, actionable fields (required_cents, plan/limit/used, a re-enable hint) — a generic catch-and-log throws that information away. Confirm your error handling branches on status code, not just on "did the fetch throw."

const res = await fetch('https://askbiz.co/api/v1/scan', opts)
if (!res.ok) {
  const err = await res.json()
  switch (res.status) {
    case 402: /* insufficient credits — err.required_cents, err.topup url */ break
    case 429: /* monthly limit — err.plan, err.limit, err.used */ break
    case 403: /* key disabled or wrong mode for this endpoint */ break
    default: /* log err.error and the status for anything else */
  }
}
5

Live keys are separate from sandbox keys in every environment

Confirm your staging environment uses a sandbox key and production uses a live key — via distinct environment variables, not a single key swapped by hand before deploys. A sandbox key accidentally left in production silently stops working against real inventory; a live key accidentally used in staging silently spends real money.

6

You’ve read the actual scopes a connection grants

If you request merchant connections, confirm your code checks which scopes were actually approved (a merchant can narrow them) rather than assuming the full set you requested was granted. Today read_inventory is the only scope, but code that assumes "whatever I asked for" rather than checking the response will break the moment a second scope exists.

7

Webhook delivery latency is accounted for

Webhooks deliver via a 5-minute cron sweep, not instantly. If any part of your integration assumes near-real-time delivery — a UI that polls for a webhook-driven update within seconds — that assumption will produce a confusing user-facing delay. Design for minutes, not milliseconds.

8

Your own monitoring exists independent of AskBiz’s dashboard

The developer dashboard shows your usage, but it won’t alert you. Confirm you have your own alerting on elevated error rates or approaching quota — from the response codes and rate-limit headers your integration already sees on every call — rather than discovering a problem when a merchant reports it.

What’s next

For the full contract behind items 1 and 4, see Errors and retries. For item 3, see Subscribe to webhooks. For the pricing and quota numbers referenced throughout, see Understanding billing and pricing.

Questions about going to production

Is there a staging environment I should be testing against?+

There’s no separate staging API — sandbox keys serve that purpose against the same production endpoints, returning realistic simulated responses with no real debit, message, or charge.

Does AskBiz page me if my integration starts failing?+

No — the dashboard shows your own usage and error history, but there’s no proactive alerting on AskBiz’s side for your integration’s error rate. That monitoring is your responsibility, built from the same status codes and headers your integration already receives.