Event reference

Every event type, when it fires, and the exact shape of its data payload.

On this page

Event types#

TypeProductFires when
payment.completedPaymentsOne copy was bought from your user. One event per copy.
payment.failedPaymentsA basket bought nothing. A partial basket's successes already announced themselves.
order.completedPaymentsA pop-up session ended: the total for the order, or abandoned.
payout.order_completedPayoutsAn order was placed, with its lines and what was charged.
withdrawal.updatedPayoutsA delivered copy changed state. One event per copy per state.
payout.failedPayoutsA copy's delivery was blocked by our own holds (delivery_blocked).
webhook.testBothYou pressed Send test event in the console.

payment.completed#

data
{
  "payment": {
    "id": "6a93b48a38abc7ce614b60c4",
    "reference": "pay_6a93b48a38abc7ce614b60c4",
    "orderId": "dep_9f2c1",
    "metadata": { "source": "wallet" },
    "customer": {
      "userId": "6a8b0e5f2c9d4a1b3e7f9013",
      "robloxId": 5501,
      "robloxUsername": "xXShadowXx",
      "externalRef": "user_48213"
    },
    "item": { "itemValueId": "chroma_luger", "name": "Chroma Luger", "valueCents": 40000 },
    "payoutCents": 1240,
    "currency": "USD",
    "fundingSource": "mm2empire",
    "sessionId": "6a93b3f1…",
    "createdAt": "2026-09-05T13:21:04+00:00"
  }
}

fundingSource is mm2empire when we paid and the money is already in your claimable balance, or partner when your own pool paid and the item is now your stock. orderId and metadata are yours, echoed verbatim.

payment.failed#

data
{
  "failure": {
    "reference": "fail_6a93b3f1_9c1e2f3a4b5c6d7e",
    "orderId": "dep_9f2c1",
    "metadata": { "source": "wallet" },
    "sessionId": "6a93b3f1…",
    "customer": { "userId": "6a8b0e5f…", "externalRef": "user_48213" },
    "items": [
      { "code": "stock_limit_reached", "message": "you already hold the most copies of this item you allow" }
    ]
  }
}

order.completed#

data
{
  "order": {
    "orderId": "dep_9f2c1",
    "sessionId": "6a93b3f1…",
    "status": "completed",
    "metadata": { "source": "wallet" },
    "customer": { "userId": "6a8b0e5f…", "robloxId": 5501, "robloxUsername": "xXShadowXx", "externalRef": "user_48213" },
    "currency": "USD",
    "totalPayoutCents": 2480,
    "paymentCount": 2,
    "truncated": false,
    "payments": [
      { "id": "6a93b48a…", "reference": "pay_6a93b48a…", "itemValueId": "chroma_luger", "name": "Chroma Luger", "valueCents": 40000, "payoutCents": 1240, "fundingSource": "mm2empire", "createdAt": "…" },
      { "id": "6a93b4a0…", "reference": "pay_6a93b4a0…", "itemValueId": "chroma_luger", "name": "Chroma Luger", "valueCents": 40000, "payoutCents": 1240, "fundingSource": "mm2empire", "createdAt": "…" }
    ],
    "completedAt": "2026-09-05T13:24:40+00:00"
  }
}

status is abandoned when the pop-up closed without selling; totalPayoutCents is then 0 and payments empty. payments lists at most 100 entries and truncated says whether more exist; paymentCount and totalPayoutCents are always complete.

payout.order_completed#

data
{
  "order": {
    "id": "6a9d1c0e5b7f4a2d8c3e9f10",
    "orderId": "wd_99120",
    "metadata": {},
    "status": "completed",
    "customer": { "robloxId": 5501, "externalRef": "user_48213" },
    "chargedCents": 6930,
    "currency": "USD",
    "itemCount": 2,
    "items": [
      { "itemValueId": "chroma_luger", "chargeCents": 3465, "withdrawalId": "6a9d…", "status": "delivering" },
      { "itemValueId": "chroma_luger", "chargeCents": 3465, "withdrawalId": "6a9d…", "status": "delivering" }
    ],
    "createdAt": "2026-09-05T12:51:00+00:00"
  }
}

id is our order id and orderId is yours. status is completed, partial or awaiting_delivery at the moment of placing.

withdrawal.updated#

data
{
  "withdrawal": {
    "id": "6a9d1c1f…",
    "status": "completed",
    "statusReason": null,
    "itemValueId": "chroma_luger",
    "robloxId": 5501,
    "payoutOrderId": "6a9d1c0e5b7f4a2d8c3e9f10",
    "orderId": "wd_99120",
    "completedAt": "2026-09-05T13:04:12+00:00"
  }
}

One event per copy per state. status moves through delivering to completed, or to failed or cancelled. Nothing about which bot or how; the claim endpoint answers that for one customer.

payout.failed#

data
{
  "payout": {
    "id": "6a9d1c0e5b7f4a2d8c3e9f10",
    "orderId": "wd_99120",
    "metadata": {},
    "itemValueId": "chroma_luger",
    "chargeCents": 3465,
    "currency": "USD",
    "code": "delivery_blocked",
    "customer": { "robloxId": 5501, "externalRef": "user_48213" }
  }
}

The copy was bought and belongs to your user, but our fraud controls are holding it. These holds are routinely cleared within a day; credit your user anyway, wait, or contact us. Nothing is refunded automatically.

webhook.test#

data
{
  "test": {
    "reference": "test_3",
    "message": "This is a test delivery from the MM2Empire partner console. No money moved and no item changed hands.",
    "payment": { "id": "pay_test", "orderId": "test-order", "metadata": { "source": "console-test" }, "payoutCents": 1, "…": "…" }
  }
}

Its own event type, so a test can never be mistaken for money. Your handler should recognise webhook.test and do nothing but return 2xx.