***

title: Simulations (Test Clocks)
subtitle: Test subscription lifecycle behavior deterministically by controlling time advancement with virtual test clocks.
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.paymentkit.com/guides/integration/llms.txt. For full documentation content, see https://docs.paymentkit.com/guides/integration/llms-full.txt.

# Overview

Simulations provide a deterministic testing environment for subscription lifecycle flows. Unlike production subscriptions that run asynchronously, simulations execute all evaluations synchronously within a single database transaction, allowing you to fast-forward time and observe exact state changes.

**Key features:**

* **Virtual time control**: Advance time to specific moments without waiting
* **Deterministic execution**: Same input state always produces the same effects
* **Full transaction isolation**: All changes happen atomically — simulations never affect production data
* **Event timeline**: Complete audit trail of every state change and evaluation
* **Synchronous evaluation**: No background workflows — results are immediate and predictable

## Use cases

| Scenario                        | How simulations help                                                                               |
| ------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Testing renewal flows**       | Jump to the next billing date and verify invoice creation, payment attempts, and state transitions |
| **Dunning behavior validation** | Advance through retry schedules to verify subscription transitions and email notifications         |
| **Trial expiration**            | Fast-forward to trial end date and confirm payment collection or cancellation                      |
| **Contract renewals**           | Validate fixed-term subscription behavior when `total_billing_cycles` completes                    |
| **Scheduled changes**           | Test pending plan changes by advancing to their scheduled execution date                           |

***

# Create a simulation

Create a test clock with a virtual timeline for deterministic subscription testing.

<CodeBlocks>
  <CodeBlock title="Request">
    ```bash
    curl -X POST https://api.paymentkit.com/api/simulations \
    -H "Authorization: Bearer sk_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "account_external_id": "acc_test_abc123",
      "start_time": "2024-01-01T00:00:00Z",
      "description": "Test subscription renewal flow"
    }'
    ```
  </CodeBlock>

  <CodeBlock title="Response">
    ```json
    {
      "external_id": "sim_test_xyz789",
      "account_external_id": "acc_test_abc123",
      "virtual_clock": "2024-01-01T00:00:00Z",
      "start_time": "2024-01-01T00:00:00Z",
      "status": "active",
      "description": "Test subscription renewal flow"
    }
    ```
  </CodeBlock>
</CodeBlocks>

**Request parameters:**

| Field                 | Type     | Required | Description                                   |
| --------------------- | -------- | -------- | --------------------------------------------- |
| `account_external_id` | string   | Yes      | Account this simulation belongs to            |
| `start_time`          | datetime | Yes      | When the simulation's virtual timeline begins |
| `description`         | string   | No       | Human-readable description for tracking       |

**Important:** All entities created within a simulation (subscriptions, invoices, payments) are automatically linked to the simulation and isolated from production data via the `simulation_id` foreign key.

***

# Create test subscriptions

Once you have a simulation, create subscriptions that will be evaluated on the virtual timeline.

```bash
curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
  "customer_id": "cus_test_abc123",
  "items": [
    {
      "price_id": "price_test_monthly",
      "quantity": 1
    }
  ],
  "simulation_id": "sim_test_xyz789",
  "start_date": "2024-01-01T00:00:00Z",
  "trial_end": "2024-01-15T00:00:00Z"
}'
```

**Key points:**

* Pass the `simulation_id` to link the subscription to the simulation
* The subscription's `start_date` should align with or follow the simulation's `start_time`
* All subscription evaluations will use the simulation's `virtual_clock` instead of real time

***

# Advance simulation time

Move the virtual clock forward to trigger subscription evaluations and state changes.

<CodeBlocks>
  <CodeBlock title="Request">
    ```bash
    curl -X POST https://api.paymentkit.com/api/simulations/sim_test_xyz789/advance \
    -H "Authorization: Bearer sk_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "target_time": "2024-02-01T00:00:00Z"
    }'
    ```
  </CodeBlock>

  <CodeBlock title="Response">
    ```json
    {
      "simulation_external_id": "sim_test_xyz789",
      "new_virtual_clock": "2024-02-01T00:00:00Z",
      "subscriptions_evaluated": 3,
      "events_recorded": 12
    }
    ```
  </CodeBlock>
</CodeBlocks>

## How time advancement works

When you advance time, PaymentKit:

1. **Validates the target**: `target_time` must be in the future relative to the current `virtual_clock`
2. **Updates the virtual clock**: Sets `virtual_clock` to `target_time`
3. **Evaluates subscriptions**: Finds all subscriptions linked to this simulation and evaluates each one using the new `virtual_clock`
4. **Executes effects synchronously**: All effects (invoice creation, payment attempts, state transitions) run immediately in a single transaction
5. **Records events**: Logs each evaluation and effect to the simulation's event timeline
6. **Returns results**: Reports how many subscriptions were evaluated and events recorded

**Important invariants:**

* **Time causality**: Virtual clock only moves forward (never backward)
* **Determinism**: Same subscription state + same target time → same effects
* **Atomicity**: All evaluations complete or the entire operation rolls back
* **Synchronous execution**: All subscription evaluations and effects execute immediately within a single transaction

***

# Retrieve simulation details

Fetch the current state of a simulation.

<CodeBlocks>
  <CodeBlock title="Request">
    ```bash
    curl -X GET https://api.paymentkit.com/api/simulations/sim_test_xyz789 \
    -H "Authorization: Bearer sk_test_..."
    ```
  </CodeBlock>

  <CodeBlock title="Response">
    ```json
    {
      "external_id": "sim_test_xyz789",
      "account_external_id": "acc_test_abc123",
      "virtual_clock": "2024-02-01T00:00:00Z",
      "start_time": "2024-01-01T00:00:00Z",
      "status": "active",
      "description": "Test subscription renewal flow"
    }
    ```
  </CodeBlock>
</CodeBlocks>

***

# View simulation timeline

Retrieve a chronological log of all events that occurred during time advancement.

<CodeBlocks>
  <CodeBlock title="Request">
    ```bash
    curl -X GET https://api.paymentkit.com/api/simulations/sim_test_xyz789/events \
    -H "Authorization: Bearer sk_test_..."
    ```
  </CodeBlock>

  <CodeBlock title="Response">
    ```json
    {
      "simulation_external_id": "sim_test_xyz789",
      "events": [
        {
          "event_type": "subscription_evaluated",
          "virtual_timestamp": "2024-01-15T00:00:00Z",
          "entity_type": "subscription",
          "entity_id": "sub_test_abc123",
          "payload": {
            "current_state": "trialing",
            "next_action": "end_trial",
            "scheduled_for": "2024-01-15T00:00:00Z"
          }
        },
        {
          "event_type": "invoice_created",
          "virtual_timestamp": "2024-01-15T00:00:01Z",
          "entity_type": "invoice",
          "entity_id": "inv_test_xyz456",
          "payload": {
            "amount_due": 1999,
            "currency": "usd",
            "subscription_id": "sub_test_abc123"
          }
        },
        {
          "event_type": "payment_attempted",
          "virtual_timestamp": "2024-01-15T00:00:02Z",
          "entity_type": "payment",
          "entity_id": "pay_test_def789",
          "payload": {
            "status": "succeeded",
            "amount": 1999,
            "currency": "usd"
          }
        },
        {
          "event_type": "subscription_transitioned",
          "virtual_timestamp": "2024-01-15T00:00:03Z",
          "entity_type": "subscription",
          "entity_id": "sub_test_abc123",
          "payload": {
            "from_state": "trialing",
            "to_state": "active"
          }
        }
      ]
    }
    ```
  </CodeBlock>
</CodeBlocks>

**Event types:**

| Event Type                  | Description                                              |
| --------------------------- | -------------------------------------------------------- |
| `subscription_evaluated`    | Subscription lifecycle engine evaluated the subscription |
| `invoice_created`           | Renewal or prorated invoice was created                  |
| `invoice_finalized`         | Draft invoice transitioned to open                       |
| `payment_attempted`         | Payment collection was attempted                         |
| `payment_succeeded`         | Payment completed successfully                           |
| `payment_failed`            | Payment failed (triggers dunning)                        |
| `subscription_transitioned` | Subscription moved to a new state                        |
| `dunning_retry_scheduled`   | Next payment retry was scheduled                         |

***

# Testing patterns

## Test case: Trial expiration with successful payment

```bash
# 1. Create simulation starting Jan 1
curl -X POST https://api.paymentkit.com/api/simulations \
  -d '{"account_external_id": "acc_test_123", "start_time": "2024-01-01T00:00:00Z"}'

# Response: sim_test_abc

# 2. Create subscription with 14-day trial
curl -X POST https://api.paymentkit.com/api/acc_test_123/subscriptions \
  -d '{
    "customer_id": "cus_test_456",
    "items": [{"price_id": "price_monthly", "quantity": 1}],
    "simulation_id": "sim_test_abc",
    "trial_end": "2024-01-15T00:00:00Z"
  }'

# 3. Advance to trial end date
curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-01-15T00:00:00Z"}'

# 4. Verify subscription transitioned to active
curl -X GET https://api.paymentkit.com/api/acc_test_123/subscriptions/sub_test_xyz

# Expected: status = "active", trial_end reached, first invoice paid
```

## Test case: Dunning retry schedule

```bash
# 1. Create simulation and subscription (same as above)

# 2. Advance to renewal date with payment failure
curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-02-01T00:00:00Z"}'

# 3. Verify subscription moved to past_due
curl -X GET https://api.paymentkit.com/api/acc_test_123/subscriptions/sub_test_xyz
# Expected: status = "past_due"

# 4. Advance to first retry (1 hour later)
curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-02-01T01:00:00Z"}'

# 5. Check event timeline for retry attempt
curl -X GET https://api.paymentkit.com/api/simulations/sim_test_abc/events

# 6. Advance to second retry (4 days later)
curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-02-05T00:00:00Z"}'
```

## Test case: Fixed-term contract completion

```bash
# 1. Create subscription with 3 billing cycles, auto-renew disabled
curl -X POST https://api.paymentkit.com/api/acc_test_123/subscriptions \
  -d '{
    "customer_id": "cus_test_456",
    "items": [{"price_id": "price_monthly", "quantity": 1}],
    "simulation_id": "sim_test_abc",
    "total_billing_cycles": 3,
    "contract_auto_renew": false
  }'

# 2. Advance through 3 billing cycles
curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-02-01T00:00:00Z"}'  # Month 1 renewal

curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-03-01T00:00:00Z"}'  # Month 2 renewal

curl -X POST https://api.paymentkit.com/api/simulations/sim_test_abc/advance \
  -d '{"target_time": "2024-04-01T00:00:00Z"}'  # Month 3 renewal (final)

# 3. Verify subscription ended after 3 cycles
curl -X GET https://api.paymentkit.com/api/acc_test_123/subscriptions/sub_test_xyz
# Expected: remaining_billing_cycles = 0, billing stopped
```

***

# Limitations and differences from production

| Aspect                  | Production                                     | Simulation                                              |
| ----------------------- | ---------------------------------------------- | ------------------------------------------------------- |
| **Execution**           | Asynchronous background processing via Restate | Synchronous, immediate execution within transaction     |
| **Retries**             | Automatic retries with exponential backoff     | Deterministic, no retries                               |
| **Timing**              | Real-time delays and schedules                 | Virtual clock (instant time jumps)                      |
| **Rollback**            | Changes are permanent once committed           | Full rollback on error (single transaction)             |
| **Domain events**       | Published to event stream in real-time         | Published to event stream with virtual timestamps       |
| **Webhooks**            | Delivered to customer endpoints in real-time   | Delivered to customer endpoints with simulation context |
| **Email notifications** | Sent to customers via Restate workflows        | Sent to customers via same email delivery system        |

**Important:** Simulations achieve 100% behavior parity with production subscription logic, including event publishing, webhook delivery, and email notifications. The key difference is execution mode: production uses async Restate workflows while simulations execute synchronously within a single transaction.

***

# Best practices

<Accordion title="Use simulations for integration tests">
  Create a simulation before each test, advance time to trigger the scenario, then assert on the resulting subscription/invoice state. Simulations are isolated and can be safely run in parallel.
</Accordion>

<Accordion title="Always verify via API, not just events">
  The event timeline shows what happened, but always fetch the subscription/invoice state via the API to confirm the final result matches expectations.
</Accordion>

<Accordion title="Test edge cases with multiple subscriptions">
  Create multiple subscriptions in the same simulation with different billing dates, trial periods, and dunning settings. Advance time once and verify all subscriptions evaluated correctly.
</Accordion>

<Accordion title="Clean up after testing">
  Simulations and their linked entities are isolated from production, but they consume database space. Delete test simulations after validation is complete (future API will support simulation deletion).
</Accordion>

<Accordion title="Use descriptive descriptions">
  The `description` field helps track what each simulation is testing. Use names like "Dunning exhaustion test" or "Trial → Active transition with proration" to make timelines easier to debug.
</Accordion>

<Callout intent="warning" title="Simulations are for testing only">
  Simulations trigger domain events, webhooks, and email notifications just like production, but execute synchronously in controlled virtual time. Never use simulations for production subscriptions — they exist solely for deterministic testing environments where you can fast-forward time and verify exact state transitions.
</Callout>