> ## Documentation Index
> Fetch the complete documentation index at: https://docs.proofage.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox and test data

> Test workspaces are free and never analysed: finish a verification, set its outcome, replay the webhook.

Build and test against a **test workspace**. It behaves like a live one, with two differences: it is never billed, and nothing you submit is analysed. You decide every outcome.

| | Test workspace | Live workspace |
| - | - | - |
| Keys | `pk_test_…` / `sk_test_…` | `pk_live_…` / `sk_live_…` |
| Analysis | None: a submitted verification goes straight to `review`. | Every check of the workspace type runs. |
| Outcome | You set it: on the verification's page in the console, with the MCP tool `set-test-verification-outcome`, or with `POST /v1/verifications/{id}/test-outcome`. | Decided by the checks, or by a person for `review`. |
| Billing | Never billed. | Billed per submitted verification. |
| Mode | Fixed when the workspace is created. | Fixed when the workspace is created. |

## Run a verification end to end

<Steps>
  <Step title="Create a test workspace">
    In the console, create a workspace in **test** mode with the check you are integrating, and copy its keys. With an AI agent, the MCP tool `create-workspace` does the same.
  </Step>

  <Step title="Create a verification and complete it">
    Create a verification from your backend as in the [quick start](/getting-started/quick-start), open its `url`, and go through the widget with a real camera. A person has to do this step: the camera screens cannot be automated.

    Images are still checked when they are uploaded, in test mode too: the selfie must show a face and the document must look like a document. Everything after the upload is skipped.
  </Step>

  <Step title="See it wait in review">
    On submit, a test verification goes straight to `review` and sends the `review` webhook.
  </Step>

  <Step title="Set the outcome">
    Choose **Approve**, **Decline** or a resubmission with a reason on the verification's page in the console, call the MCP tool `set-test-verification-outcome` with `approved`, `declined` or `resubmission_requested`, or [set it from your code](#set-the-outcome-from-your-code). The verification moves to that status and sends its webhook, so your handler sees exactly what a live decision would send.
  </Step>

  <Step title="Check your handler">
    Confirm that your handler verified the signature, updated the user and answered `2xx`. The workspace's webhook deliveries in the console show each attempt with your response.
  </Step>
</Steps>

## Set the outcome from your code

To test your handler without a person going through the widget, for example in CI, set the outcome through the API:

```bash theme={null}
POST /v1/verifications/{verification}/test-outcome
```

```json theme={null}
{ "status": "approved" }
```

* `status` is `approved`, `declined`, `review` or `resubmission_requested`.
* `reason` is optional: a note kept with a `resubmission_requested` outcome in the verification's history. It is not a decision reason, and the webhook's `reason` stays `null`.
* It works on any verification that is not final, including one nobody has opened. Such a verification is moved through `started` and `submitted` first, as a person's submission would move it, so exactly one decision webhook is sent: the outcome's.
* It answers with the verification, as `GET /v1/verifications/{id}` does.
* A live workspace answers `403 TEST_WORKSPACE_ONLY`. A verification that is already `approved`, `declined`, `abandoned` or `expired` answers `422 INVALID_STATUS`, and so does `review` for a verification already in review.

## Receiving webhooks locally

Your development machine is not reachable from the internet, so ProofAge cannot deliver webhooks to `localhost`. Two ways to receive them while you build:

* **A tunnel.** Expose your local handler with a tunnel such as ngrok or Cloudflare Tunnel and set the tunnel's HTTPS URL as the test workspace's webhook URL.
* **Replay.** Leave the webhook URL pointing anywhere, then use the MCP tool `get-webhook-delivery-request`: it returns the exact request ProofAge sends, signed with the active secret key, ready to replay against `localhost` with curl. Send the body byte for byte; a re-serialised body fails the signature check.

## What a test workspace cannot show you

Because nothing is analysed, test verifications never produce:

* a decision reason: `reason` is `null` on every test outcome (the text you give with a resubmission is kept as a note in the console, not sent as a code),
* duplicate detection or blocklist matches,
* an age estimate or document data from the analysis: the `document` object in the webhook is there, with every field `null`.

Test those paths with the documented payloads instead (the field shapes of `document` are in [Webhooks](/integration/webhooks#payload)): the [Decision webhook](/api-reference/webhook-decision) example, the [Decision reasons](/core-technology/decision-reasons) list, and the duplicate block described in [Webhooks](/integration/webhooks#payload). When you are ready, follow the [Go-live checklist](/integration/go-live).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.