---
updatedAt: 2026-10-05T20:11:18.000Z
agentTools:
  projectIndex: https://docs.checkhq.com/llms.txt
---

# Understanding Sandbox and Production Environments

Check provides two separate environments to support your integration journey: **Sandbox** for development and testing, and **Production** for live payroll operations. While we strive to keep these environments aligned, there are important differences you should understand when building a robust integration.

## What is the Sandbox Environment?

The sandbox environment is a safe testing space where you can:

* Develop and test your Check integration
* Experiment with API calls without affecting real companies or employees
* Validate workflows before going live
* Train your team on Check's features

## Important Differences to Know

### Company Setup and Onboarding

Sandbox has automatic company status progression to help you see all lifecycle stages:

* Companies automatically advance through implementation status phases every 2 minutes
* The full implementation status lifecycle is: `needs_attention` → `in_review` → `completed`
* Additionally, each entity (company, employee, contractor) has an `onboard.status` (`blocking`, `needs_attention`, or `completed`) that determines whether payroll can run. These statuses ***do not*** automatically advance but must instead be advanced by resolving blockers, which are stored in `onboard.remaining_steps` and `onboard.blocking_steps`.

**Validation rules relaxed in sandbox:**

* **EIN/FEIN verification:**
  * All EIN verifications automatically succeed in sandbox. In production, the real IRS verification runs — after 3 failed attempts, the EIN reaches `final_rejected` status and cannot be retried
  * To simulate EIN verification failure, create a company where **both** conditions are true:
    * `legal_name` starts with `reject-fein-verification` (e.g., `"reject-fein-verification-test"`)
    * `federal_ein` starts with `12-345678` (e.g., `"12-3456789"`)
    * Both sentinel values must match — if only one is set, the verification will auto-succeed as normal. When both match, the sandbox skips real verification and auto-rejects instead:
      * **Attempts 1 and 2:** Status is set to `rejected` (retryable — your integration should prompt the user to correct their info and try again)
      * **Attempt 3:** Status is set to `final_rejected` (permanent — no more retries allowed, the company cannot proceed without Check support intervention)
    * This lets you test the full rejection lifecycle and verify your integration handles both the retryable and terminal states.
* **Bank account verification:** Company bank accounts are automatically set to `ownership_verified` and employee accounts to `validated`. In production, this process takes \~3 banking days via ACH prenote. Use routing number `000000000` to simulate a disabled bank account in sandbox, or `111111111` to simulate failure for both company and employee accounts.
* **Payroll approval gating:** In sandbox, payrolls can run even if the company or employees have `blocking` onboard statuses. In production, payroll is blocked for any participant with `onboard.status = "blocking"`.

**Common blocking onboard requirements (enforced in production, skipped in sandbox by default):**

For companies:

* `bank_account` — bank account must exist, be verified (`ownership_verified`), and have a debit authorization submitted
* `setup_parameters` — federal and state tax parameters must be fully configured
* `filing_authorization` — federal and state filing authorization forms must be submitted

**What this means for you:** Companies that succeed in sandbox may encounter validation failures in production. If you want sandbox to enforce production-like payroll approval gating, reach out to your Check representative to enable the feature flag on your account.

**Example production-only error responses:**

When a company has incomplete onboarding:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "company_blocking",
    "message": "Company has blocking onboard requirements: bank_account, filing_authorization"
  }
}
```

When EIN verification is permanently rejected (after 3 failed attempts):

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "federal_ein_verification_rejected",
    "message": "Federal EIN verification has reached final_rejected status and cannot be retried"
  }
}
```

When an employee has a blocking onboard status:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "employee_blocking",
    "message": "Employee has blocking onboard requirements that must be resolved before running payroll"
  }
}
```

### Risk and Compliance

* Companies are **automatically approved** for risk reviews in sandbox as compliance checks are bypassed

**What risk review looks like in production:**

When a company is submitted for enrollment, Check performs KYB (Know Your Business) verification. The company’s `implementation.status` will be `in_review` during this period, which can take up to 2 business days. If a risk issue is identified:

* The company’s `in_good_standing` field is set to `false`, which prevents payroll from running
* Attempting to run payroll for an affected company returns a `company_not_in_good_standing` error
* If KYB is fully denied, `implementation.status` will not advance to `completed`

A risk decision is not the only thing that takes a company out of good standing. Check puts a company out of good standing for one of five reasons, and this is the complete set: a failed debit, a late wire, fraud risk, compliance risk, and credit risk.

**Sandbox limitations:** There is no simulation endpoint to trigger a risk review block. To prepare your integration:

* Handle the `in_good_standing = false` state on the company object
* Display appropriate messaging when `company_not_in_good_standing` or `company_not_implemented` errors are returned
* Direct users to contact support for risk holds — resolution requires Check intervention

**Example: Company blocked by risk review:**

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "company_not_in_good_standing",
    "message": "Company 'com_sx3svU6K8c5ZkSFlOh5p' is not in good standing"
  }
}
```

The company object will show `"in_good_standing": false`. The API does not tell you which of the five reasons applies. The one exception is a failed debit, which also appears as a `resolve_funding_failure` requirement you can read and track — see [Resolve Funding Failures](https://docs.checkhq.com/docs/resolve-funding-failures). For the other four reasons, direct users to Check.

**What this means for you:** You cannot fully test risk review workflows in sandbox. Design your user experience for these scenarios upfront and verify your error handling covers `company_not_in_good_standing` before going live

### Money Movement

Sandbox **simulates** all payment processing meaning no real money moves through banks and physical checks are not printed.

**How it works:**

| Aspect                 | Sandbox                                                                                                    | Production               |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------ |
| Payment processing     | Simulated (auto-completes)                                                                                 | Real ACH processing      |
| Payment simulation     | Available via the [start processing endpoint](https://docs.checkhq.com/reference/sandbox-start-processing) | Not available            |
| Money movement         | **No** - simulated                                                                                         | **Yes** - real transfers |
| Failed payment testing | Available via the [fail funding endpoint](https://docs.checkhq.com/reference/sandbox-fail-funding)         | Real payment failures    |

**What this means for you:** Use our [simulate endpoints](https://docs.checkhq.com/docs/testing-payments-in-sandbox) to test various payment scenarios.

**Simulating a full payroll payment lifecycle in sandbox:**

1. Create and approve a payroll (status becomes `pending`)
2. Either wait for auto-transition (every 5 minutes, for approved payrolls only), or manually control each step:
   * Set `simulation_mode = "manual"` on the payroll to disable auto-transitions
   * `POST /payrolls/{id}/simulate/start_processing` → status: `processing`
   * `POST /payrolls/{id}/simulate/complete_funding` → status: `paid`

Auto-transitions apply to **approved payrolls only** — if you need to inspect a specific intermediate state, switch to manual mode before approving.

**Simulating specific failure modes:**

* **NSF (Non-Sufficient Funds):** Create a company with `trade_name = "NSF Co."` or `"No Account Co."` — triggers an R01 (Insufficient Funds) ACH return
* **Failed employee disbursement:** Create an employee with `first_name = "Barry"` and `last_name = "BadAccount"` — triggers an R03 (No Account/Unable to Locate) ACH return
* **Predicted funding failure (NSF bucketing):** Create a company with `trade_name = "High Risk Co."` or `"High Risk Missing Co."` — triggers the predicted funding failure flow, simulating Check's NSF risk model flagging a company before the debit is sent
* **Failed funding:** Call `POST /payrolls/{id}/simulate/fail_funding` after the payroll reaches `processing` status

**Payment reversals:**

In sandbox, payment reversals are simulated instantly — a dummy reversal object is created and the payment is immediately marked as reversed, with no bank interaction. In production, reversals are submitted as real ACH reversal requests through the payment processor, and settlement timing depends on the banking network (typically 1–2 business days). Design your integration to handle the asynchronous nature of production reversals rather than assuming the instant sandbox behavior

### Validation Rules

Sandbox has fewer validation rules to make testing easier.

**Duplicate detection:**

Sandbox does not enforce duplicate validation at the platform level. In production:

* **FEIN:** Duplicate federal EINs across companies are detected and flagged
* **SSN:** Employees with duplicate SSNs across the platform generate a `duplicate_ssn` blocked filing reason, which can prevent tax filings from being submitted

**Blocking validation enforcement:**

In sandbox, blocking validations are not enforced — validation failures that would raise hard exceptions in production instead log warnings and allow the operation to proceed. For example, a child support garnishment missing a required SDU (State Disbursement Unit) configuration will fail with an error in production, but silently pass in sandbox. This can mask configuration issues that only surface after go-live.

**Field-level validation differences:**

* Some fields accept any value in sandbox (e.g., EIN format is not strictly validated) but have format constraints in production
* SSN format validation rules are identical in both environments: invalid SSNs (starting with 9, 666, or 000; middle digits of 00; last four digits of 0000; or sequential/repeated patterns) are rejected in both environments

**What this means for you:** Implement your own validation logic even if sandbox accepts your data. Production enforces all blocking validations, and partners regularly encounter issues at go-live that were not surfaced in sandbox testing. See the [Company Setup and Onboarding](#company-setup-and-onboarding) section above for the full list of blocking onboard requirements and how to enable production-parity payroll approval gating in sandbox

### Feature Availability

Features can be enabled independently in each environment:

* New features often roll out to production after sandbox testing
* Some features require explicit enablement in your sandbox account

**Common features that need explicit enablement (contact your Partner Engineer or Growth Manager):**

* **Processing periods:** `two_day` and `one_day` ACH processing must be enabled by Check
* **Payment methods:** Paper checks, same-day ACH, and wire funding availability depend on your contract
* **Third-party partner integrations:** Vestwell (401k), SimplyInsured (health insurance), and Next Insurance (workers' comp) each require a contract amendment and Check-side configuration
* **Tax jurisdictions:** Sandbox supports **all** US states and jurisdictions by default, but production restricts operations to a specific set of \~50 supported states. If you build and test with a state in sandbox that isn't yet supported in production, you'll encounter errors at go-live. Verify your required jurisdictions are production-enabled before launch.

There is no self-serve API endpoint to check which features are enabled in your account. Before going live, verify with your Growth Manager or Partner Engineer that all features your integration relies on are active in production

### Time-Dependent Logic

Sandbox auto-processes payrolls without enforcing real timing constraints, but production has strict funding deadlines you need to account for.

**Production ACH funding deadlines:**

| **Processing Period** | **Approval Deadline**                      |
| --------------------- | ------------------------------------------ |
| `three_day`           | 3:00 PM ET, 3 banking days before pay date |
| `two_day`             | 8:00 PM ET, 2 banking days before pay date |
| `one_day`             | 8:00 PM ET, 1 banking day before pay date  |

**Wire funding deadlines:**

* Payrolls funded by wire must be approved by **4:00 PM ET on pay date**
* Wires not received by **12:30 PM ET on pay date** will cause the payroll to automatically fail
* A failed wire takes the company out of good standing, blocking future payrolls until the wire arrives or the payroll is fully voided

**Example: Payroll approved past deadline:**

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "payroll_approval_deadline_passed",
    "message": "The approval deadline for this payroll has passed. The pay date must be updated."
  }
}
```

**Approximating time-dependent behavior in sandbox:**

* Use `POST /payrolls/{id}/simulate/start_processing` and `POST /payrolls/{id}/simulate/complete_funding` to manually step through payroll states without waiting for real deadlines
* Use `POST /payrolls/{id}/simulate/fail_funding` to test the wire failure / late wire flow
* There are no sandbox configuration options to adjust auto-progression timing

**What this means for you:** Design your integration to handle funding deadline scenarios. Communicate cutoff times clearly to your users and build alerting for payrolls approaching their approval deadlines.

### Third-Party Partnerships

Integrations with Check’s ecosystem partners vary in their sandbox testability. All partner integrations require a contract amendment and Check-side configuration before testing in sandbox or deploying to production.

| **Partner**                          | **Type**  | **Sandbox Testable?**     | **Notes**                                                                                                                                                                                                                                             |
| ------------------------------------ | --------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Vestwell (401k)**                  | Component | Requires setup            | Check must configure Vestwell staging environment. You can read more about the transition from Accrue [here](https://help.checkhq.com/partnerships--integrations/4Msp39koQozoq6pAPPZsWZ/accrue-%E2%86%92-vestwell-transition/6V14NkP6tEYpyN1eSyrxeu). |
| **SimplyInsured** (Health Insurance) | Component | Requires setup            | Check must stage the integration in SimplyInsured. Allow up to 2 weeks. May require a contract amendment.                                                                                                                                             |
| **Next Insurance** (Workers’ Comp)   | Component | Available with dummy data | Test data inputs are available to go through the full quote funnel. Requires Check setup; allow up to 2 weeks. May require a contract amendment.                                                                                                      |

**What this means for you:** Contact your Check account team early if you plan to integrate any ecosystem partners — setup can take 2+ weeks.

### API Keys and Authentication

Each environment has completely separate authentication:

**Sandbox:**

* Use sandbox API keys
* Points to [`sandbox.checkhq.com`](http://sandbox.checkhq.com)
* Data is completely isolated from production

**Production:**

* Use production API keys
* Points to [`api.checkhq.com`](http://api.checkhq.com)
* Processes real payroll for real companies

Never mix credentials between environments.

### Webhook Handling

Webhooks behave differently in each environment. Sandbox webhooks may be slower to deliver than production which have higher throughput capacity. Webhook configs are also kept isolated so there is no cross-environment webhook delivery.

**Retry logic comparison:**

| Aspect                   | Sandbox                  | Production                          |
| ------------------------ | ------------------------ | ----------------------------------- |
| Retry attempts           | 1 retry                  | Up to 18 attempts                   |
| Retry duration           | After 1 retry, abandoned | \~47 hours with exponential backoff |
| Initial wait             | 10 seconds               | 10 seconds                          |
| Max wait between retries | 6 hours                  | 6 hours                             |
| Check-Live header        | "false"                  | "true"                              |

**Using the `Check-Live` header:**

All webhook events include a `Check-Live` header with value `"false"` in sandbox and `"true"` in production. Use this to gate real-money logic in your webhook handler so that a single handler can safely serve both environments:

```jsx
app.post('/webhooks/check', (req, res) => {
  const isLive = req.headers['Check-Live'] === 'true';
  if (isLive) {
    // Handle real production event
  } else {
    // Handle sandbox test event
  }
});
```

**Webhook delivery during sandbox inactivity:** Sandbox webhook delivery does not queue indefinitely. If your endpoint is unavailable during sandbox testing, events are abandoned after the single retry and are not recoverable — use the manual retry API to redeliver specific events.

**Webhook event types:** The set of webhook topics and event types is identical between sandbox and production — there are no events that only fire in one environment. The same topics (e.g., `payroll`, `payment`, `employee`, `company`) and event types (`created`, `updated`, `deleted`, `status_change`) are available in both environments. The differences between environments are purely operational (delivery behavior, retry policies, and development convenience endpoints like the manual retry API).

**Webhook config auto-disable:** Check automatically disables webhook configs that consistently fail delivery. In sandbox, configs are disabled after **1 day** of continuous failures. In production, the lookback window is **7 days**. If your sandbox webhook endpoint goes down briefly during development, your config may be auto-disabled faster than you'd expect — check the `active` field on your webhook config if you stop receiving events.

**Design recommendations:**

* Don't hardcode webhook timing assumptions
* Implement robust retry logic for all webhook-triggered workflows (don't rely on Check's retry logic alone)
* Monitor webhook delivery status via the API
* Test webhook failures using the [manual retry API](https://docs.checkhq.com/reference/retry-a-webhook-delivery-in-sandbox) in sandbox

### Tax Filings and Tax Payments

Tax filing and tax payment behavior differs significantly between environments. In sandbox, filings can be generated and their lifecycle simulated, but critical production-only processes do not run.

**Production-only processes (do not run in sandbox):**

* **Filing sync:** The workflow that syncs filing data with tax agencies runs only in production. Sandbox filings exist in Check's system but are never transmitted to any tax authority.
* **Employee tax statement publishing:** W-2s, 1099s, and other employee tax statements are only published and synced in production.
* **Tax payment orchestration:** Actual tax payments to agencies (federal, state, local) are only processed in production. Sandbox tax liabilities are calculated but no money is remitted.
* **Tax payment monitoring:** Alerts for failed or late tax payments only fire in production.

**What you can test in sandbox:**

* Verifying that your integration correctly reads filing status and blocked reasons
* Reading filing data and tax liability calculations (these are computed in both environments)

**What this means for you:** You can test your integration's handling of filing status changes and blocked reasons in sandbox, but you cannot verify end-to-end filing submission or tax payment flows. Ensure your integration handles the `blocked` filing status and displays blocked reasons to users, as these are common in production (e.g., `duplicate_ssn`, missing tax parameters).

### Sandbox-Only API Endpoints

These endpoints are only available in sandbox and return **404 in production**:

| Endpoint                                                                  | Purpose                                                                                                                    |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [\[Sandbox\] Start processing](ref:sandbox-start-processing)              | Simulate starting payroll processing (pending → processing); requires `simulation_mode` = `manual` and a `pending` payroll |
| [\[Sandbox\] Complete funding](ref:sandbox-complete-funding)              | Simulate successful funding settlement (processing → paid); requires `simulation_mode` = `manual`                          |
| [\[Sandbox\] Complete disbursements](ref:sandbox-complete-disbursements)  | Simulate disbursement completion; requires `simulation_mode` = `manual`                                                    |
| [\[Sandbox\] Fail funding](ref:sandbox-fail-funding)                      | Simulate funding failure; requires `simulation_mode` = `manual`; works on processing, paid, or partially\_paid payrolls    |
| [Retry webhook event in sandbox](https://docs.checkhq.com/reference/retry-a-webhook-delivery-in-sandbox) | Retry a failed webhook delivery for testing                                                                                |

### Rate Limits and Data Constraints

Both environments enforce the same kinds of limits, but the default request rate differs:

* **Production:** the request rate limit scales with the number of payees you paid in the prior calendar month — see [Rate limiting](https://docs.checkhq.com/docs/rate-limiting) for the tier table
* **Sandbox:** **10 requests per second** per partner (across all API keys)
* **100 concurrent in-flight requests** per partner (both environments)

The lower sandbox default is intentional: it makes sandbox the right place to exercise your `429` handling and retry logic before it matters in production. When limits are exceeded, the API returns HTTP `429` with a `Retry-After` header and error type `throttled`.

**Payroll data limits (same in both environments):**

* Maximum **500 payroll items** (employees) per payroll with synchronous preview, or **2,500** with asynchronous preview on API version `2025-01-01` or later
* Maximum **500 contractor payments** per payroll with synchronous preview, or **2,500** with asynchronous preview on API version `2025-01-01` or later
* Three-day processing payrolls: **$1,000,000** maximum; above this, wire funding is required
* Federal tax liability: **$100,000** per deposit period maximum before wire funding is required

**Sandbox data retention:** Sandbox data is not periodically purged — records persist until explicitly deleted via the API. There is no enforced limit on the number of companies, employees, or payrolls you can create in sandbox.

***

## Pre-Production Checklist

Before going live, verify the following:

**Authentication & Configuration**

* [ ] Update API base URL from `sandbox.checkhq.com` to `api.checkhq.com`
* [ ] Replace sandbox API keys with production API keys
* [ ] Register webhook endpoints in the production environment

**Integration Verification**

* [ ] Confirm all required features are enabled in production (contact your Partner Engineer or Growth Manager)
* [ ] Verify third-party partner integrations are fully configured for production
* [ ] Remove or guard all calls to sandbox-only simulation endpoints

**Error Handling**

* [ ] Implement handling for `company_blocking` errors (incomplete onboarding) and `company_not_in_good_standing` errors (standing holds)
* [ ] Handle `in_good_standing = false` — the API does not say which reason applies, so surface any open `resolve_funding_failure` requirement and direct users to Check otherwise
* [ ] Handle EIN verification failures and the `final_rejected` state
* [ ] Handle bank account verification delays (\~3 banking days in production)
* [ ] Handle `onboard.status = "blocking"` for employees and contractors before running payroll
* [ ] Implement retry logic for webhook processing (don't rely on Check's retry alone — sandbox only retries once)
* [ ] Handle payment reversal asynchronously (instant in sandbox, 1–2 business days in production)
* [ ] Handle filing `blocked` statuses and display blocked reasons (e.g., `duplicate_ssn`)

**UX Design**

* [ ] Design UX for risk review states (`implementation.status = in_review`) — can take up to 2 business days
* [ ] Display funding deadlines to users and communicate approval cutoffs
* [ ] Handle the full company implementation lifecycle: `needs_attention` → `in_review` → `completed`
* [ ] Use the `Check-Live` header in your webhook handler to distinguish sandbox from production events
* [ ] Design for the case where a state/jurisdiction is not yet supported in production

For more information on the differences between sandbox and production, reach out to your Partner Engineer.