Sandbox

Sandbox-Simulate header

Drive deterministic outcomes (paid / failed / expired / chargeback / slow_payment) from CI via a single request header. No UI needed.

The playground at /sandbox/login is great for hands-on debugging, but CI / automated tests need a programmatic way to drive outcomes. Pass the Sandbox-Simulate header on POST /api/v1/payments and we schedule the transition for you — the same state machine the playground uses, no clicks required.

The header is honoured ONLY in sandbox. In production it's silently ignored — a misconfigured test suite can't accidentally drive live data.

Accepted values

ValueDelayOutcomeWebhooks fired
paid5 spending → completedpayment.completed
failed5 spending → failedpayment.failed
expired30 spending → expiredpayment.failed
chargeback30 s + 5 spending → completed → chargebackpayment.completed,
chargeback.created
slow_payment5 minpending → completed (very late)payment.completed

Usage

bash
# Create a payment that auto-pays in 5 seconds:
curl https://sandbox.key2pays.com/api/v1/payments \
  -H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -H "Sandbox-Simulate: paid" \
  -d '{ "amount": 50, "paymentMethodId": "1008", "country": "MEX" }'

# Create a payment that auto-fails in 5 seconds:
curl https://sandbox.key2pays.com/api/v1/payments \
  -H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
  -H "Sandbox-Simulate: failed" \
  -d '{ … }'

# Create a payment that completes then disputes — perfect for testing
# your chargeback.created webhook handler:
curl https://sandbox.key2pays.com/api/v1/payments \
  -H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
  -H "Sandbox-Simulate: chargeback" \
  -d '{ … }'

Typical CI pattern

javascript
// Pattern: create tx with simulate, poll until terminal status, assert.

async function testHappyPath() {
  // Spin up a local webhook receiver before this test (e.g. ngrok),
  // register it once during test setup, share across tests.

  const created = await api.post("/payments", {
    headers: { "Sandbox-Simulate": "paid" },
    body: { amount: 50, paymentMethodId: "1008", country: "MEX" },
  });
  expect(created.status).toBe("pending");

  // Wait for the webhook (up to 10s — the simulate fires at 5s):
  const event = await webhookReceiver.next("payment.completed", 10000);
  expect(event.data.id).toBe(created.transactionId);

  // Confirm the tx now reads as completed:
  const fetched = await api.get(`/payments/${created.transactionId}`);
  expect(fetched.status).toBe("completed");
}