> This page is for version v1 (default).
> For other versions, use one of these documentation indexes:
> - v1 (default): https://developers.truemed.com/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.truemed.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.truemed.com/_mcp/server.

# Testing

> Run a full token lifecycle in the sandbox — provision, charge, break, and recover — before going live.

Truemed's sandbox lets you run the whole subscription lifecycle without moving real funds: provision a
token, charge it, force it into a blocked state, and recover it. The flow is identical to production;
only the credentials and base URL differ.

Test the failure paths, not just the happy path. A subscription integration that has never handled a
`422` will break on its first expired LMN.

## Set up the sandbox

| Step                                                                                      | Where                                                                                     |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Create a **sandbox API key**                                                              | [dev.truemed.com/developers/api-keys](https://dev.truemed.com/developers/api-keys)        |
| Point requests at the **dev base URL**                                                    | `https://dev-api.truemed.com/`                                                            |
| Add a sandbox webhook for **both** `payment_session_complete` and `payment_token_updated` | [dev.truemed.com/developers/webhooks](https://dev.truemed.com/developers/webhooks/manage) |

A sandbox key only works against the dev base URL. See [Before You Begin](/guides/setup/api/payment-sessions/before-you-begin) for the full setup.

## Provision a test token

1. Create a payment session against dev with `tokenize: true`, exactly as in [New Subscriber](/guides/setup/api/subscriptions/create-a-subscription-new-subscriber#create-the-session).

2. Open the `redirect_url` and complete the health survey as the customer would.

3. Enter the test card—no real funds move:

   * **Card number:** 4242 4242 4242 4242
   * **Expiration:** any future date
   * **CVC / ZIP:** any values

4. In the **Payments** tab of the [dev dashboard](https://dev.truemed.com), open the **Actions** menu
   on the test payment and choose **Approve LMN**.

5. Confirm both webhooks arrive: `payment_session_complete` with `status: captured`, and
   `payment_token_updated` carrying the `payment_token`.

> **Note**
>
> Store the `payment_token` from the webhook. The rest of this page uses it. If `payment_token_updated`
> never arrives, the LMN wasn't approved—no token was provisioned.

## Charge the token

Charge it the way your billing job will, with no customer present:

```bash
curl https://dev-api.truemed.com/payments/v1/create_payment_session \
  -X POST \
  -H "Content-Type: application/json" \
  -H "x-truemed-api-key: $TRUEMED_API_KEY" \
  -d '{
    "payment_token": "dev_tm_token_...",
    "total_amount": 4200,
    "success_url": "https://example.com/account/orders",
    "failure_url": "https://example.com/account/orders",
    "idempotency_key": "test_period_2",
    "order_items": [
      { "name": "Monthly B12", "quantity": 1, "sku": "b12-monthly", "price": 4200, "amount_details": {} }
    ],
    "amount_details": {}
  }'
```

There's nothing to approve this time—a recurring charge runs no new survey and issues no new LMN, so it
bills against the approved LMN already on file and the dashboard offers no Approve action. Confirm
`payment_session_complete` reports `status: captured`. That's a complete billing period.

## Drive a failure

Use the **Actions** menu on a test payment to push the token into a blocked state.

| Dashboard action | What it does                                     | Use it to test                                                                 |
| ---------------- | ------------------------------------------------ | ------------------------------------------------------------------------------ |
| **Approve LMN**  | Issues the LMN; the payment proceeds             | Your fulfillment path on `captured`                                            |
| **Reject LMN**   | Rejects the LMN; the session moves to `rejected` | Your cancel-and-notify path, and confirming no token is provisioned            |
| **Expire LMN**   | Approves the LMN, then marks it expired          | The `TakeHealthSurvey` path—charge the token afterwards and you'll get a `422` |

To test the requalification loop end to end: expire the LMN, charge the token, confirm you get `422`
with `next_action: "TakeHealthSurvey"`, send yourself to the `redirect_url`, complete the survey,
confirm `payment_token_updated` arrives with a `failed_payment_session_id`, then charge again with a
new payment session.

Also confirm your handler treats `payment_token_updated` as a stand-down signal and clears whatever
warning you raised. See [Token Lifecycle](/guides/setup/api/subscriptions/token-lifecycle#recover-the-subscription).

> **Note**
>
> There's no dashboard lever for forcing an expired or declined card, so a charge that fails that way
> can't be driven end to end. The closest you can get is calling
> [`update_payment_token`](/api-reference/payment-tokens/update-payment-token) with
> `update_card_info: true`, which returns the same `next_action` and `redirect_url` shape. That
> exercises your update-card handling only—it is not a failed charge, and no `422` is involved.

## Before you go live

* [ ] **Swap credentials and base URL.** Production key, and `https://api.truemed.com/`.
* [ ] **Recreate both webhooks in production.** `payment_session_complete` and `payment_token_updated`.
  Missing the second one means you never receive tokens.
* [ ] **`next_action` is wired to customer messaging.** Both `UpdatePaymentMethod` and
  `TakeHealthSurvey` reach the customer with the `redirect_url`.
* [ ] **The expiration-monitoring job is scheduled.** See [Prevent failures before they happen](/guides/setup/api/subscriptions/subsequent-billing-periods#prevent-failures-before-they-happen).
* [ ] **Fulfillment is idempotent.** Webhook retries must not duplicate shipments, emails, or credits.
* [ ] **Item changes call `update_payment_token`.** And you only confirm the change when `next_action`
  comes back `"None"`.
* [ ] **Walk the [Implementation Readiness Checklist](/guides/setup/api/payment-sessions/implementation-readiness-checklist).**