Complete reference for every webhook event StellarKit emits: what triggers it, the exact payload shape, and what each field means.
For registering, authenticating, and verifying webhooks, see webhooks.md. This document covers only the events themselves.
- Event index
- Delivery envelope
payment.receivedtrustline.changedcontract.event- Adding a new event type
| Event | Fires when | Emitted by |
|---|---|---|
payment.received |
A payment or create_account operation touches an account with an open payment stream |
src/routes/stream.js |
trustline.changed |
A trustline is created, removed, updated, or has its authorization flags changed | src/services/trustlineChangeDetector.js |
contract.event |
A Soroban contract emits an on-chain event | src/services/contractEventPoller.js |
Subscription matching is exact string equality on the event name. Register
with the precise names above — payment will not receive payment.received
deliveries.
Every event is delivered as an HTTP POST with a JSON body.
Headers on every delivery:
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
StellarKit-Webhook/1.0 |
X-Webhook-Event |
The event field from the JSON body |
Fields common to every payload:
| Field | Type | Description |
|---|---|---|
event |
string |
The event name. Always present, so one endpoint can fan-in multiple types. |
payment.received and trustline.changed additionally carry accountId and a
top-level timestamp. contract.event carries neither — it is scoped to a
contract, not an account. See each section below.
Delivery and retries: a delivery has a 30 second timeout. Any non-2xx response, timeout, or network error counts as a failed attempt, retried with exponential backoff at 5 s, 25 s, and 125 s — 4 attempts total, after which the delivery is marked permanently failed. Retries are in-process and are abandoned on restart, so your endpoint must be idempotent: the same event may arrive more than once.
A payment credited to, or debited from, an account being streamed.
Fires while an SSE stream is open on GET /stream/payments/:id, for each
Horizon payment operation on that account whose type is one of:
payment— a standard payment operationcreate_account— account creation, where the starting balance is the payment
Operations of any other type (path payments, offers, and so on) are streamed to
the SSE client but do not trigger this webhook. The event fires only if at
least one active webhook is registered for the account under the exact event
name payment.received.
{
"event": "payment.received",
"accountId": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN",
"payment": {
"type": "payment",
"amount": "100.0000000",
"asset": {
"code": "USDC",
"issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
"type": "credit_alphanum4"
},
"from": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
"to": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN",
"timestamp": "2026-08-26T12:00:00Z"
},
"timestamp": "2026-08-26T12:00:01.482Z"
}| Field | Type | Description |
|---|---|---|
event |
string |
Always "payment.received". |
accountId |
string |
The streamed account — the :id from the stream URL, not necessarily the payment recipient. |
payment.type |
string |
"payment" or "create_account". |
payment.amount |
string | null |
Seven-decimal amount. For create_account this is the starting balance. null if Horizon reported neither. |
payment.asset.code |
string |
Asset code, or "XLM" for native. |
payment.asset.issuer |
string | null |
Issuer public key, or null for native. |
payment.asset.type |
string |
"native", "credit_alphanum4", or "credit_alphanum12". |
payment.from |
string | null |
Sender. For create_account this is the funder. Falls back to the operation's source account. |
payment.to |
string | null |
Recipient. For create_account this is the newly created account. |
payment.timestamp |
string | null |
ISO 8601 close time of the ledger containing the operation. |
timestamp |
string |
ISO 8601 time StellarKit built the payload. Later than payment.timestamp. |
Native XLM always serialises as
{ "code": "XLM", "issuer": null, "type": "native" }. All amounts are
seven-decimal strings — parse them as decimals, not floats.
A change to an account's trustline for an issued asset.
Fires when transaction effects are processed for a monitored account and an effect is one of the following Horizon effect types:
| Horizon effect | Resulting changeType |
|---|---|
trustline_created |
added |
trustline_removed |
removed |
trustline_updated |
updated |
trustline_flags_updated |
updated |
trustline_authorized |
authorization_changed |
trustline_deauthorized |
authorization_changed |
The effect must reference the monitored account as its account, trustor, or
trustee. Effects without both an asset_code and an asset_issuer are
skipped, so native XLM and liquidity-pool shares never produce this event.
{
"event": "trustline.changed",
"accountId": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN",
"trustline": {
"asset": {
"code": "USDC",
"issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
"type": "credit_alphanum4"
},
"balance": "10.0000000",
"limit": "922337203685.4775807",
"isAuthorized": true,
"buyingLiabilities": "0.0000000",
"sellingLiabilities": "0.0000000"
},
"changeType": "added",
"timestamp": "2026-08-26T12:00:00.000Z",
"transactionHash": "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c1234"
}| Field | Type | Description |
|---|---|---|
event |
string |
Always "trustline.changed". |
accountId |
string |
Account whose trustline changed — the effect's account, else its trustor. |
trustline.asset.code |
string |
Asset code from the effect. |
trustline.asset.issuer |
string |
Issuer public key. Always present; assets without an issuer are skipped. |
trustline.asset.type |
string |
"credit_alphanum4" or "credit_alphanum12". Defaults to "credit_alphanum4" when Horizon omits it. |
trustline.balance |
string |
Seven-decimal balance held. "0.0000000" when the effect carries no balance, which is the case for a removal. |
trustline.limit |
string |
Seven-decimal trust limit. "0.0000000" when absent. |
trustline.isAuthorized |
boolean | null |
Whether the issuer has authorized the trustline. null when the effect does not report authorization. |
trustline.buyingLiabilities |
string |
Seven-decimal buying liabilities, "0.0000000" when absent. |
trustline.sellingLiabilities |
string |
Seven-decimal selling liabilities, "0.0000000" when absent. |
changeType |
string |
added, removed, updated, or authorization_changed. See the mapping table above. |
timestamp |
string |
ISO 8601 time the effects batch was processed. Shared across every event from the same batch. |
transactionHash |
string |
Hash of the transaction that produced the effect. |
A single transaction can produce several trustline.changed deliveries — one
per matching effect. Use transactionHash to group them.
A Soroban smart contract emitted an on-chain event.
The contract event poller calls the Soroban RPC getEvents on an interval
(CONTRACT_POLL_INTERVAL_MS, default 10000 ms), filtered to contract type
events, starting from the ledger after the last one it saw. Each returned event
is normalised into the payload below.
Current status: contract events are polled and normalised, but delivery to subscribers is not yet wired up —
webhookDelivery.deliverContractEventhas no subscriber registry behind it and logs instead of dispatching. The payload shape below is the contract to build against; it is what will be delivered once subscriber lookup lands. The poller also requires a configured Soroban RPC endpoint and does nothing without one.
{
"event": "contract.event",
"contractId": "CCJZ5DGASBWQXR5MPFCJXMBI333XE5U3FSJTNQU7RIKE3P5GN2K2WYD2",
"eventType": "transfer",
"topic": ["transfer", "alice", "bob"],
"value": "100",
"ledger": 12345
}| Field | Type | Description |
|---|---|---|
event |
string |
Always "contract.event". |
contractId |
string |
Contract that emitted the event (C…). |
eventType |
string |
First topic, stringified. By Soroban convention this is the event name symbol. "unknown" when the event has no topics. |
topic |
array |
All topics, decoded from ScVal to native JS values in emission order. |
value |
any | null |
The event body, decoded from ScVal. null when the event carries no value. |
ledger |
number |
Ledger sequence the event was emitted in. |
Decoding notes. topic entries and value are decoded with
scValToNative. Two consequences worth handling:
BigIntresults are converted to strings, since JSON cannot represent them. Au128amount arrives as"100", not100.- If a value cannot be decoded natively, it falls back to its base-64 XDR
string, and to
nullif even that fails. Do not assume every entry is a decoded primitive.
Unlike the other events, contract.event has no accountId and no top-level
timestamp. Use ledger for ordering.
Event documentation here follows one template. To add an event, copy the structure of an existing section:
- Add a row to Event index — name, one-line trigger, and the file that emits it.
- Add a section with the heading
## `your.event`containing, in order:- a one-line summary
### Trigger conditions— the precise conditions, including any that suppress the event### Payload— a complete, realistic JSON example, every field populated### Fields— a table ofField | Type | Description, one row per field including nested ones, with nullability shown in the type (string | null)- any decoding, formatting, or ordering notes
- Add the anchor to the Table of Contents.
- Update the event table in webhooks.md so the guide and this reference agree.
Keep field descriptions sourced from the emitting code rather than from intent — note the actual fallback value when a field can be absent, as the tables above do.
- webhooks.md — registration, authentication, signature verification, retries
- streaming.md — the SSE streams that produce
payment.received - soroban.md — Soroban contract endpoints
- error-reference.md — error envelope shapes