reward.exchange-settled
Version 1 of the reward.exchange-settled webhook event.
One exchange reached a terminal state in Reward Management: settled when the value moved (an internal Red ↔ Blue post, a native payout proven on chain, or a deposit credited from a chain-proven transfer), rejected when admission refused it durably (insufficient funds), released when a reserved payout or an unfunded deposit intent was returned, and unresolved when a successful chain receipt did not prove the transfer or a proven deposit fell below the floor. Every terminal record publishes exactly one of these, in the same transaction as its ledger effect; nothing is published for a non-terminal state.
| Event type | reward.exchange-settled |
| Schema version | 1 |
| Routing key | event.reward.exchange-settled.v1 |
| Produced by | Reward Management |
| Schema | urn:defendr:contract:reward:exchange-settled:1 |
data fields
| Field | Type | Required | Description |
|---|---|---|---|
exchangeId | string | yes | |
playerId | string | yes | |
kind | "internal", "crypto-payout", "crypto-deposit" | yes | |
outcome | "settled", "rejected", "released", "unresolved" | yes | |
chain | "solana", "ethereum", "hedera", null | yes | |
from | object | yes | |
from.currencyKey | string | yes | |
from.amount | string | yes | A whole amount for internal currencies (Red, Blue); base units (lamports, wei, tinybar-equivalent wei) for a native currency. |
to | object | yes | |
to.currencyKey | string | yes | |
to.amount | string | yes | A whole amount for internal currencies (Red, Blue); base units (lamports, wei, tinybar-equivalent wei) for a native currency. |
rate | string | yes | The configured rate pinned on the record: Blue per Red (5) or Red per Blue (0.2) for an internal exchange; Red per whole native unit for a native leg. |
dust | string | yes | The fraction of one output unit (a base unit for a payout, a whole Red for a deposit) the integer conversion could not represent, as a decimal below 1 with up to 18 digits; disclosed, never credited. |
gameSlug | string or null | yes | |
destination | string or null | yes | |
treasury | string or null | yes | The chain signer a deposit must target; null for a payout and an internal exchange. |
nonce | string or null | yes | The decimal u64 number carried by the transfer; null for an internal exchange. |
txId | string or null | yes | |
failureCode | string or null | yes | |
settledAt | string (date-time) | yes |
Some fields are required only in particular states. The JSON Schema below is authoritative.
Example delivery
Signed with the example secret whsec_placeholder_example_webhooks_signing_secret, so you can check your verifier against it. See Verifying signatures.
POST /your/webhook/endpoint HTTP/1.1
content-type: application/json
webhook-id: whd_00000000-0000-4000-8000-000000000023
webhook-timestamp: 1800000000
webhook-signature: v1,GZIcPu/XbRrsIFKNU2psjH4TdEJ4cWWJZMS0QVUCmDE=
tanitar-event-type: reward.exchange-settled
tanitar-schema-version: 1
tanitar-delivery-attempt: 1
{"messageId":"evt_00000000-0000-4000-8000-000000000023","kind":"event","type":"reward.exchange-settled","schemaVersion":1,"occurredAt":"2027-01-15T10:00:00.000Z","producer":"reward-management-service","accountId":"acc_11111111-1111-4111-8111-111111111111","correlationId":"corr_00000000-0000-4000-8000-000000000023","data":{"exchangeId":"exch_0123456789abcdef0123456789abcdef","playerId":"player_demo","kind":"crypto-payout","outcome":"settled","chain":"solana","from":{"currencyKey":"red","amount":"150"},"to":{"currencyKey":"sol","amount":"10000000"},"rate":"15000","dust":"0","gameSlug":null,"destination":"7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCTsxg","treasury":null,"nonce":"1234567890123456789","txId":"5UfDuX7WXY7f5J5xg7pGt8H2N4wYVJ6E1VZ1kY4Wc1u3vXcQm8nHqpLjsVzHt2NhbiPqk3jD5X1hfUJ3rW6z6XwR","failureCode":null,"settledAt":"2026-09-19T00:00:00.000Z"}}The same body, formatted:
{
"messageId": "evt_00000000-0000-4000-8000-000000000023",
"kind": "event",
"type": "reward.exchange-settled",
"schemaVersion": 1,
"occurredAt": "2027-01-15T10:00:00.000Z",
"producer": "reward-management-service",
"accountId": "acc_11111111-1111-4111-8111-111111111111",
"correlationId": "corr_00000000-0000-4000-8000-000000000023",
"data": {
"exchangeId": "exch_0123456789abcdef0123456789abcdef",
"playerId": "player_demo",
"kind": "crypto-payout",
"outcome": "settled",
"chain": "solana",
"from": {
"currencyKey": "red",
"amount": "150"
},
"to": {
"currencyKey": "sol",
"amount": "10000000"
},
"rate": "15000",
"dust": "0",
"gameSlug": null,
"destination": "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCTsxg",
"treasury": null,
"nonce": "1234567890123456789",
"txId": "5UfDuX7WXY7f5J5xg7pGt8H2N4wYVJ6E1VZ1kY4Wc1u3vXcQm8nHqpLjsVzHt2NhbiPqk3jD5X1hfUJ3rW6z6XwR",
"failureCode": null,
"settledAt": "2026-09-19T00:00:00.000Z"
}
}JSON Schema
urn:defendr:contract:reward:exchange-settled:1
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:defendr:contract:reward:exchange-settled:1",
"title": "Reward exchange settled event payload",
"description": "One exchange reached a terminal state in Reward Management: `settled` when the value moved (an internal Red ↔ Blue post, a native payout proven on chain, or a deposit credited from a chain-proven transfer), `rejected` when admission refused it durably (insufficient funds), `released` when a reserved payout or an unfunded deposit intent was returned, and `unresolved` when a successful chain receipt did not prove the transfer or a proven deposit fell below the floor. Every terminal record publishes exactly one of these, in the same transaction as its ledger effect; nothing is published for a non-terminal state.",
"type": "object",
"additionalProperties": false,
"required": [
"exchangeId",
"playerId",
"kind",
"outcome",
"chain",
"from",
"to",
"rate",
"dust",
"gameSlug",
"destination",
"treasury",
"nonce",
"txId",
"failureCode",
"settledAt"
],
"properties": {
"exchangeId": {
"type": "string",
"pattern": "^exch_[a-f0-9]{32}$"
},
"playerId": {
"type": "string",
"minLength": 1
},
"kind": {
"enum": [
"internal",
"crypto-payout",
"crypto-deposit"
]
},
"outcome": {
"enum": [
"settled",
"rejected",
"released",
"unresolved"
]
},
"chain": {
"enum": [
"solana",
"ethereum",
"hedera",
null
]
},
"from": {
"type": "object",
"additionalProperties": false,
"required": [
"currencyKey",
"amount"
],
"properties": {
"currencyKey": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$"
},
"amount": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "A whole amount for internal currencies (Red, Blue); base units (lamports, wei, tinybar-equivalent wei) for a native currency."
}
}
},
"to": {
"type": "object",
"additionalProperties": false,
"required": [
"currencyKey",
"amount"
],
"properties": {
"currencyKey": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$"
},
"amount": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "A whole amount for internal currencies (Red, Blue); base units (lamports, wei, tinybar-equivalent wei) for a native currency."
}
}
},
"rate": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$",
"description": "The configured rate pinned on the record: Blue per Red (`5`) or Red per Blue (`0.2`) for an internal exchange; Red per whole native unit for a native leg."
},
"dust": {
"type": "string",
"pattern": "^0(?:\\.[0-9]{1,18})?$",
"description": "The fraction of one output unit (a base unit for a payout, a whole Red for a deposit) the integer conversion could not represent, as a decimal below 1 with up to 18 digits; disclosed, never credited."
},
"gameSlug": {
"type": [
"string",
"null"
]
},
"destination": {
"type": [
"string",
"null"
]
},
"treasury": {
"type": [
"string",
"null"
],
"description": "The chain signer a deposit must target; null for a payout and an internal exchange."
},
"nonce": {
"type": [
"string",
"null"
],
"pattern": "^[0-9]+$",
"description": "The decimal u64 number carried by the transfer; null for an internal exchange."
},
"txId": {
"type": [
"string",
"null"
]
},
"failureCode": {
"type": [
"string",
"null"
],
"pattern": "^[A-Z0-9_]+$"
},
"settledAt": {
"type": "string",
"format": "date-time"
}
},
"allOf": [
{
"if": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"const": "internal"
}
}
},
"then": {
"type": "object",
"properties": {
"chain": {
"type": "null"
},
"destination": {
"type": "null"
},
"treasury": {
"type": "null"
},
"nonce": {
"type": "null"
},
"txId": {
"type": "null"
}
}
}
},
{
"if": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"const": "crypto-payout"
}
}
},
"then": {
"type": "object",
"properties": {
"chain": {
"type": "string"
},
"nonce": {
"type": "string"
},
"destination": {
"type": "string"
},
"treasury": {
"type": "null"
}
}
}
},
{
"if": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"const": "crypto-deposit"
}
}
},
"then": {
"type": "object",
"properties": {
"chain": {
"type": "string"
},
"nonce": {
"type": "string"
},
"treasury": {
"type": "string"
},
"destination": {
"type": "null"
}
}
}
},
{
"if": {
"type": "object",
"required": [
"outcome"
],
"properties": {
"outcome": {
"const": "settled"
}
}
},
"then": {
"type": "object",
"properties": {
"failureCode": {
"type": "null"
}
}
}
},
{
"if": {
"type": "object",
"required": [
"outcome"
],
"properties": {
"outcome": {
"enum": [
"rejected",
"unresolved"
]
}
}
},
"then": {
"type": "object",
"properties": {
"failureCode": {
"type": "string"
}
}
}
}
]
}