How to Use Chargeback Simulation

What This Is For

Chargeback Simulation lets you trigger dispute-like webhook events without creating real disputes at Stripe, Braintree, or Adyen.

Use it when you want to validate:

  • dispute handler logic,
  • webhook retries,
  • normalized payload shape,
  • Backoffice visibility,
  • end-to-end QA flows in sandbox or staging.

This guide is the workflow doc on top of the raw API reference. It shows:

  • how to trigger a simulation,
  • how to do it through the SDK,
  • how to wire a test callback,
  • where to inspect results in Backoffice.

The Fast Mental Model

A simulation is not a fake UI-only object. It goes through the real webhook pipeline:

  1. You create a simulation.
  2. Syllecta generates provider-shaped dispute payloads.
  3. Those payloads go through the normal webhook log/forward path.
  4. Your callback receives normalized events.
  5. Backoffice shows the simulation row and the emitted webhook events.

That is why this feature is useful for QA: it exercises the same delivery path as real webhook traffic.

Prerequisites

You need:

  • a tenant API key,
  • Backoffice access for the same tenant,
  • a callback URL that can receive JSON POSTs,
  • a decision on which provider shape you want first:
    • stripe
    • braintree
    • adyen

Before the first simulation, make sure tenant webhook configuration is already in place:

  • callback URL is set,
  • provider secret is set where the provider requires one.

If you have not done that yet, first read:

Supported Input

POST /v1/simulate/chargeback

Request body:

FieldTypeRequiredNotes
providerstripe | braintree | adyenYesChooses the generated provider-like payload shape.
amountintegerYesMinor units, for example cents.
currencystringYesLowercase ISO code such as usd or eur.
transactionIdstringNoExternal charge / transaction reference to echo in the payload.
simulateLifecyclebooleanNoDefaults to true. If false, only the initial event is emitted.
finalStatuswon | lostNoValid only when simulateLifecycle=true.

Lifecycle Behavior

When simulateLifecycle=true, the normal progression is:

  1. chargeback.created
  2. chargeback.under_review
  3. chargeback.won or chargeback.lost

Default timing:

  • created immediately
  • under_review after about 2 minutes
  • final state after about 20 minutes

When simulateLifecycle=false, only the initial created event is emitted.

Option 1. Trigger It with Raw HTTP

Stripe-shaped simulation

bash
curl -X POST https://cloud.syllecta.example/v1/simulate/chargeback \
  -H "Authorization: Bearer ck_acme_staging_demo" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "stripe",
    "amount": 2500,
    "currency": "usd",
    "transactionId": "ch_demo_123",
    "simulateLifecycle": true,
    "finalStatus": "won"
  }'

Braintree-shaped simulation

bash
curl -X POST https://cloud.syllecta.example/v1/simulate/chargeback \
  -H "Authorization: Bearer ck_acme_staging_demo" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "braintree",
    "amount": 2599,
    "currency": "usd",
    "transactionId": "bt_txn_demo_456",
    "simulateLifecycle": true,
    "finalStatus": "lost"
  }'

Typical success response:

json
{
  "ok": true,
  "simulation": {
    "id": "01164cdb-b0a3-469f-b2f5-aad33eb00da7",
    "status": "created"
  },
  "simulateLifecycle": true,
  "finalStatus": "won"
}

Option 2. Trigger It Through the SDK

Use the normal tenant API key and the simulations resource.

ts
import { BearerAuth, CloudSDK } from "@syllecta/sdk-js";
 
const sdk = new CloudSDK({
  baseUrl: "https://cloud.syllecta.example",
  auth: BearerAuth(process.env.SYLLECTA_API_KEY!)
});
 
const result = await sdk.simulations.createChargeback({
  provider: "stripe",
  amount: 2500,
  currency: "usd",
  transactionId: "ch_demo_123",
  simulateLifecycle: true,
  finalStatus: "won"
});
 
console.log(result.simulation.id, result.simulation.status);

Use the SDK path when:

  • your test harness already lives in TypeScript,
  • you want repeatable QA scripts,
  • you want the same auth/client abstraction as the rest of your Syllecta calls.

Minimal Test Callback Example

For a first integration test, use a very small callback receiver so you can confirm payload shape before wiring real business logic.

Example with Express:

ts
import express from "express";
 
const app = express();
 
app.use(express.json());
 
app.post("/webhooks/syllecta", (req, res) => {
  console.log("received normalized event", {
    id: req.body.id,
    provider: req.body.provider,
    type: req.body.type,
    transactionId: req.body.payload?.transaction_id ?? req.body.payload?.data?.transaction_id
  });
 
  res.status(200).json({ ok: true });
});
 
app.listen(5055, () => {
  console.log("test callback listening on http://localhost:5055/webhooks/syllecta");
});

Use that as your tenant callback URL:

text
http://localhost:5055/webhooks/syllecta

If you are testing from a local machine and Syllecta cannot reach localhost directly, expose it through your preferred tunnel and use the public tunnel URL in Backoffice.

Recommended First End-to-End Test

Use this order:

  1. Set the callback URL in Backoffice.
  2. Confirm tenant webhook settings are saved.
  3. Start the test callback receiver.
  4. Trigger one simulation with simulateLifecycle=false.
  5. Confirm the callback receives the created event.
  6. Trigger a second simulation with simulateLifecycle=true.
  7. Wait for under_review and final state events.

This is the safest progression because:

  • the first run validates connectivity and payload shape,
  • the second run validates lifecycle scheduling and repeated delivery behavior.

What You Should See in Backoffice

After triggering a simulation, open Backoffice and check two places.

1. Webhooks -> Simulations

You should see:

  • the simulation row,
  • provider,
  • amount and currency,
  • current simulation status,
  • lifecycle control state.

Use this tab to answer:

  • was the simulation created,
  • which step is currently active,
  • is the lifecycle still running, paused, cancelled, or completed.

2. Webhooks -> Events

You should see the emitted webhook events tied to the simulation:

  • chargeback.created
  • chargeback.under_review
  • chargeback.won or chargeback.lost

Use this tab to answer:

  • was the event logged,
  • was it forwarded successfully,
  • did the callback fail or retry,
  • what normalized payload was actually sent.

Short rule:

  • Simulations tab = simulation-level state
  • Events tab = per-webhook delivery state

How to Read the Results

If everything is healthy:

  • the simulation row advances through expected statuses,
  • the event rows reach processed,
  • your callback receives the normalized payloads.

If the simulation exists but no event reaches your system:

  • check callback URL,
  • inspect event delivery status in Backoffice,
  • inspect callback logs.

If the first event works but later lifecycle steps do not:

  • check whether the simulation was paused or cancelled,
  • check lifecycle timing expectations,
  • inspect event rows for failed deliveries or retries.

Billing Note

Simulation events go through the webhook delivery pipeline, so they behave like real webhook traffic from an execution perspective.

Important billing behavior:

  • simulation delivery is billed only after successful callback processing,
  • if webhook billing is disabled for the tenant, simulation traffic does not increment billable usage,
  • a full lifecycle run can produce up to three delivered events.

Good QA Patterns

Start with deterministic samples

Set transactionId explicitly so it is easy to trace in logs and downstream systems.

Separate staging from production

Do not mix simulation flows into production callbacks unless that is an intentional drill.

Use one simple callback before real business logic

Validate transport and payload shape first. Then wire the same route into domain handlers.

Test both one-shot and lifecycle mode

You need both:

  • simulateLifecycle=false for simple callback smoke tests,
  • simulateLifecycle=true for state progression and retry behavior.

Common Mistakes

Expecting the Simulations tab to replace the Events tab

It does not. You need both views.

Forgetting to configure the callback URL first

The simulation can still be created, but you will not validate useful downstream behavior.

Running only full lifecycle tests

Start with one-step smoke tests first. They fail faster and are easier to debug.

Using production-facing business handlers immediately

Use a test callback first if you are still validating payload structure.

Troubleshooting

If the simulation request itself fails:

  • check API key,
  • check tenant permissions,
  • check payload validation.

If the simulation request succeeds but callback delivery fails:

  • inspect Webhooks -> Events,
  • check callback URL reachability,
  • confirm the callback returns 2xx.

If lifecycle steps do not arrive:

  • verify simulateLifecycle=true,
  • wait for the configured lifecycle windows,
  • inspect event history and simulation control state in Backoffice.

Related Docs

This is the workflow guide. Use the API reference when you need exact request/response contract details, and use Backoffice when you need to inspect the resulting simulation and webhook deliveries.