Integration

How to wire Taxes into an existing checkout: calculate, round-trip taxCalculationId through your PSP, commit after payment succeeds. For field-level request and response shapes, use the API reference. For what a calculation or transaction means, see Calculations and Transactions.

Checkout sequence

Authenticate, calculate, charge on your PSP, then commit. The critical handoff is round-tripping taxCalculationId through the PSP so the payment webhook can reconcile the paid order to the quote before you commit. Auth details live on Authentication, and endpoint schemas live in the API reference.

Steps

  • Authenticate - get/refresh an access token (cache with TTL)
  • Calculate - POST /api/tax/calculations, capture taxCalculationId, show taxAmount at checkout
  • Round-trip - attach taxCalculationId to the PSP payment as metadata/custom field
  • Pay - charge via your PSP (Stripe, Adyen, Nuvei, …)
  • Commit - on payment success webhook, POST /api/tax/transactions with taxCalculationId and payment details
  • Clear - e-invoice / authority clearance runs asynchronously after commit

PSP round-trip

After Calculate, attach taxCalculationId to the payment request (e.g. Stripe metadata, Nuvei customField1). When the payment webhook fires, read that field back and call commit. Without the round-trip you cannot reliably connect a successful charge to the tax shown at checkout.

Pseudocode

async function processCheckout(orderData) {
  const token = await getAccessToken();

  const taxResponse = await calculateTax(orderData, token);
  const taxCalculationId = taxResponse.taxCalculationId;
  await persistCalculationMapping(orderData.orderId, taxCalculationId);

  const paymentResult = await processPayment(orderData, {
    amount: taxResponse.totalAmount,
    metadata: { taxCalculationId },
  });

  // Often from the PSP webhook - only after payment succeeds
  if (paymentResult.success) {
    await storeTransaction({
      taxCalculationId,
      merchantTransactionReference: orderData.orderId,
      payment: {
        paymentReference: paymentResult.paymentReference,
        processor: paymentResult.processor, // e.g. "stripe", "adyen", "nuvei"
      },
    }, token);
  }

  return { success: paymentResult.success, taxResponse };
}

Idempotency

  • Persist merchantTransactionReference → taxCalculationId before charging
  • Reuse an existing mapping instead of creating a second calculation for the same order
  • Implement mutex/locks for concurrent order processing
  • Use database constraints to prevent duplicate transactions
  • Reusing a merchantTransactionReference returns 400 invalid_state on that field, not the original transaction - treat it as "already stored" and read the transaction you recorded
  • A calculation can be converted once. Reusing it returns 400 invalid_state on taxCalculationId

Error handling

Classify by HTTP status. Do not retry 4xx blindly - they are client mistakes. Token refresh on 401 is covered under Authentication.

4xx - client

  • 401 - refresh the token and retry once (see Authentication)
  • 400 invalid_argument - fail fast (malformed fields, legacy tax-id shapes like type "CPF" without a jurisdiction prefix)
  • 400 invalid_state - calculation expired, already used, or product not activated
  • 404 - calculation not found (check taxCalculationId on commit)
  • 409 - configuration or coverage problem, not a client bug. Do not retry; the region, product or provider integration needs changing
  • 422 nfse_validation_failed - Brazil NFS-e rejected the customer data. Fix the data before retrying

5xx - server

Usually temporary. Retrying a read is always safe; retrying a write needs a duplicate check first.

  • 502 tax_provider_unavailable - the provider is temporarily down. Safe to retry with exponential backoff (e.g. 1s, 2s, 4s, with jitter)
  • Cap retries - typically three attempts - then fail and alert
  • 504 request_timeout on commit - the transaction may still have been created. Do not blind-retry; check by merchantTransactionReference first

Tax identifiers

  • Identifier types are jurisdiction-prefixed: BR_CPF, BR_CNPJ, EU_VAT, GB_VAT, US_EIN - shapes without the prefix return 400 invalid_argument
  • CPF/CNPJ checks are format and check-digit only - a format-valid ID can still fail later at NFS-e issuance
  • Malformed calculate requests return a structured 400 with field-level errors - not a 500

Token refresh on 401

// On 401 response:
// 1. Clear cached token
// 2. Request new access token
// 3. Retry the original request once with new token
// 4. If still 401, fail and alert

Structured logging

{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "INFO",
  "message": "Tax calculated",
  "orderId": "order-12345",
  "taxCalculationId": "calc-67890",
  "currency": "USD",
  "taxAmount": 8.25,
  "requestId": "req-uuid-123",
  "duration": 1200
}

Metrics & alerting

  • Success rates - percentage of successful calculate/commit operations
  • Latency - P95/P99 response times for each endpoint
  • Error rates - 4xx vs 5xx error breakdown by endpoint
  • Token refresh frequency - monitor auth token lifecycle

Alert conditions

  • Success rate drops below 95%
  • P95 latency exceeds 2 seconds
  • 5xx error rate exceeds 1%
  • Commit rate significantly lower than calculate rate
  • Token refresh failures

Testing & rollout

Prove calculate → round-trip → commit against staging, then enable commit gradually in production.

Environments

There are two environments, staging and production, each with its own credentials. Build and test against staging, then switch both base URLs and the credential pair to go live. There is no separate sandbox environment and no test mode within production.

EnvironmentAPI base URLAuth base URL
Staginghttps://api.outpostnow.techhttps://access.outpostnow.tech
Productionhttps://api.outpostanywhere.comhttps://access.outpostanywhere.com

What to test

Format-valid Brazilian IDs still do not guarantee NFS-e issuance - registration is confirmed only when the e-invoice is submitted.

  • Auth - token acquisition and refresh
  • Calculate - include a valid-format tax identifier for Brazil (BR_CPF / BR_CNPJ) - omitting it can silently return zero tax
  • Commit - only after a successful payment, with the same taxCalculationId
  • Errors - exercise timeout, retry, and representative 4xx/5xx cases

Rollout

Phase 1Calculate only (commit disabled) until quotes look right
Phase 2Enable commit for 1% of successful payments
Phase 3Gradually increase to 10%, 50%, 100%
RollbackFeature flags to disable commit calls quickly if needed

Acceptance checklist

  • Auth token acquisition works
  • Token caching and refresh logic implemented
  • Calculate returns a taxCalculationId
  • taxCalculationId round-trips through the PSP and is stored with the order
  • Commit includes paymentReference and processor
  • Commit runs only after successful payment
  • Brazil cases include a valid-format CPF/CNPJ
  • Timeout and retry logic implemented
  • 4xx fail fast; 5xx retry with backoff
  • Logging includes correlation IDs
  • Monitoring and alerting configured

API map

Need a single pasteable brief for an AI coding assistant?

One-click brief for your AI agent.