OpenPoly logo
Webhooks

Event types

Business events sent by OpenPoly to operator webhook receivers.

Event types

Current event list comes from packages/mini-app/src/types.ts.

Source-of-truth list

  • mini_app.login.succeeded
  • mini_app.trade.submitted
  • mini_app.trade.processed
  • mini_app.trade.failed
  • mini_app.trade.compensation_required
  • mini_app.redemption.processed
  • mini_app.redemption.failed
  • mini_app.payout.pending
  • mini_app.logout.requested
  • mini_app.session.expired
  • mini_app.polyflight.purchase.processed
  • mini_app.polyflight.purchase.failed
  • mini_app.polyflight.ticket.resolved
  • mini_app.polyflight.payout.pending
  • mini_app.polyflight.payout.processed
  • mini_app.polyflight.refund.pending
  • mini_app.polyflight.refund.processed
  • mini_app.polyflight.purchase.compensated

Payload reference

Current durable callback delivery reconstructs data at send time for aggregate_type = mini_app_order. That means trade callback data is operator-actionable even if the stored outbox payload is minimal.

Trade order data fields

Trade events with aggregate_type = mini_app_order use this data family:

  • order_id
  • trade_id
  • executed_trade_id
  • operator_id
  • operator_user_id
  • polynion_user_id
  • external_user_id
  • market_id
  • side: buy or sell
  • outcome: yes or no
  • status
  • requested_external_amount_minor
  • requested_shares_micro
  • quoted_credits_equivalent
  • quoted_external_amount_minor
  • shares_micro
  • balance_spent
  • trade_fee
  • trade_fee_credits
  • currency_code
  • fx_rate_snapshot_id
  • failure_reason

Do not expect legacy credits_delta or external_amount_minor in trade callback data.

trade_fee is historical fee in integer minor units of currency_code, matching Operator Trade Fees view. trade_fee_credits is same fee denominated in Credits.

mini_app.login.succeeded

Allowlisted event name for launch/session lifecycle. Current durable processor paths do not define an operator-actionable payload beyond the common envelope.

mini_app.trade.submitted

Allowlisted event name for order submission. Current durable trade processor callbacks use the processed, failed, or compensation-required terminal events below.

mini_app.trade.processed

Sent when trade completed successfully.

{
  "order_id": 123,
  "trade_id": 456,
  "executed_trade_id": 456,
  "operator_id": 12,
  "operator_user_id": 34,
  "polynion_user_id": 789,
  "external_user_id": "operator-user-123",
  "market_id": 51,
  "side": "buy",
  "outcome": "yes",
  "status": "processed",
  "requested_external_amount_minor": 10000,
  "requested_shares_micro": null,
  "quoted_credits_equivalent": 6340,
  "quoted_external_amount_minor": 10000,
  "shares_micro": 1234500,
  "balance_spent": 10000,
  "trade_fee": 39,
  "trade_fee_credits": 25,
  "currency_code": "IDR",
  "fx_rate_snapshot_id": 88,
  "failure_reason": null
}

mini_app.trade.failed

Sent when trade fails and no successful final execution exists.

{
  "order_id": 123,
  "trade_id": null,
  "executed_trade_id": null,
  "operator_id": 12,
  "operator_user_id": 34,
  "polynion_user_id": 789,
  "external_user_id": "operator-user-123",
  "market_id": 51,
  "side": "buy",
  "outcome": "yes",
  "status": "failed",
  "requested_external_amount_minor": 10000,
  "requested_shares_micro": null,
  "quoted_credits_equivalent": 6340,
  "quoted_external_amount_minor": 10000,
  "shares_micro": null,
  "balance_spent": 10000,
  "trade_fee": null,
  "trade_fee_credits": null,
  "currency_code": "IDR",
  "fx_rate_snapshot_id": null,
  "failure_reason": "OPERATOR_DEBIT_FAILED"
}

mini_app.trade.compensation_required

Sent when wallet debit happened but downstream completion needed manual or compensating handling.

{
  "order_id": 123,
  "trade_id": null,
  "executed_trade_id": null,
  "operator_id": 12,
  "operator_user_id": 34,
  "polynion_user_id": 789,
  "external_user_id": "operator-user-123",
  "market_id": 51,
  "side": "buy",
  "outcome": "yes",
  "status": "compensation_pending",
  "requested_external_amount_minor": 10000,
  "requested_shares_micro": null,
  "quoted_credits_equivalent": 6340,
  "quoted_external_amount_minor": 10000,
  "shares_micro": null,
  "balance_spent": 10000,
  "trade_fee": null,
  "trade_fee_credits": null,
  "currency_code": "IDR",
  "fx_rate_snapshot_id": null,
  "failure_reason": "DB_EXECUTION_FAILED_AFTER_DEBIT"
}

mini_app.redemption.processed

Sent when redemption or payout completed successfully.

{
  "redemption_item_id": 77,
  "run_id": 9,
  "redeem_request_id": 88,
  "operator_id": 12,
  "operator_user_id": 34,
  "external_amount_minor": 15000,
  "fx_rate_snapshot_id": 91
}

mini_app.redemption.failed

Sent when redemption flow failed before successful credit completion.

{
  "redemption_item_id": 77,
  "run_id": 9,
  "operator_id": 12,
  "operator_user_id": 34,
  "status": "manual_review",
  "failure_reason": "OPERATOR_CREDIT_FAILED"
}

mini_app.payout.pending

Allowlisted event name for payout follow-up. Current Mini App redemption processor emits mini_app.redemption.failed with status = manual_review for unresolved payout-side failures.

mini_app.logout.requested

Allowlisted event name for operator-side session cleanup. Current durable processor paths do not define an operator-actionable payload beyond the common envelope.

mini_app.session.expired

Allowlisted event name for launched Mini App session expiry. Current durable processor paths do not define an operator-actionable payload beyond the common envelope.

Polyflight event semantics

EventContract
mini_app.polyflight.purchase.processedNative debit and ticket admission completed. Do not debit from the webhook.
mini_app.polyflight.purchase.failedNo ticket was admitted. status can be failed, manual_review, or compensation_pending; reconcile the purchase debit instead of assuming whether money moved.
mini_app.polyflight.ticket.resolvedTicket outcome and payout amount are final; this does not assert wallet delivery. Newly settled losses have payoutMinor: 0 and a non-null delivery obligationId, not money owed.
mini_app.polyflight.payout.pendingA winning payout is owed, or its wallet credit is in manual_review.
mini_app.polyflight.payout.processedWinning wallet credit is confirmed.
mini_app.polyflight.refund.pendingA cancelled-ticket full refund is owed, or its wallet credit is in manual_review.
mini_app.polyflight.refund.processedCancelled-ticket wallet credit is confirmed.
mini_app.polyflight.purchase.compensatedAn applied debit with no admitted ticket was reversed.

The signed envelope stays snake_case; Polyflight data keys are camelCase. Initial pending events use status = "owed" and do not signal a failed credit. A later pending event with status = "manual_review" includes failureReason. Processed events confirmed from operator evidence add manualResolution: true.

New Polyflight event snapshots include operatorUserId, externalUserId, currencyCode, stakeMinor (gross debit or purchase quote), feeMinor (ticket fee or quote), netStakeMinor, and potentialPayoutMinor. Purchase failures without a ticket report quoted economics only; they do not claim a debit or earned fee. Admitted-ticket events additionally include feeBps, payoutMultiplier, commercialTermsVersionId, ticketFeeShareBps, and positionShareBps; the last two describe the captured operator commercial allocation, not extra fees charged to the user. A cancelled-ticket refund reverses the full gross stake, including its fee. A no-ticket compensation reverses the full applied debit, with no earned ticket fee.

amountMinor on a pending payout/refund or compensation failure is owed, not yet confirmed cash; on a processed payout/refund or compensated purchase it is confirmed wallet credit/reversal. payoutMinor on ticket resolution is the result (0 for a loss), not proof of credit. status belongs to the event-time snapshot: later payment or manual resolution does not change it on webhook replay. Event omissions on older deliveries mean unknown, never zero; read API, events, and proof for precise fields.

The eight Polyflight events are unchanged. A new loss's zero-amount /credit acknowledgement is tracked by the ticket/payment lifecycle and loss_credit wallet operation; no loss payout pending/processed callback exists. ticket.resolved remains authoritative regardless of acknowledgement status. Historical loss payloads can retain obligationId: null; webhook replay does not backfill a loss delivery or rewrite planned/dispatched wallet evidence.

Use event_id to deduplicate delivery. Wallet debit, credit, and reversal requests have separate financial idempotency keys; a webhook must never trigger a second money movement. See API, events, and proof for the source-backed payload families, full envelope example, and handling table.

Guidance

  • keep receiver tolerant to additive fields
  • branch only on known event_type
  • use event_id for dedupe
  • for current trade callbacks, prefer order_id, trade_id / executed_trade_id, operator_user_id, external_user_id, and market_id for support correlation
  • for current redemption callbacks, prefer redemption_item_id, run_id, redeem_request_id, operator_user_id, and external_amount_minor when present
  • treat failure_reason as operator-facing diagnostic string, not enum guaranteed forever
  • examples above are source-backed where current contract already defines fields explicitly
Copyright © 2026