Skip to content

Events reference

Rolink pushes events to every live server of your experience through Roblox Open Cloud MessagingService, using your project’s own settings. The SDK subscribes to the same topic and turns each event into a signal. For patterns and examples, see the Live events guide.

typeSDK signalSent when
wallet_linkedWalletLinkedA player links a wallet on the project’s linking page.
wallet_unlinkedWalletUnlinkedA player unlinks, or loses their wallet to another account.
txTransactionUpdatedA transaction sent by the managed wallet reaches confirmed or failed.
watchChainActivityA new transaction touches one of the project’s watched addresses.
pingnoneYou send a test event from the dashboard.

Events are published per project, only when the project has both settings, under Settings in the dashboard:

  • Universe ID. The experience’s universe ID, digits only. Not a place ID.
  • Open Cloud API key. A key from Creator Hub with messaging-service → universe-messaging-service:publish on that experience. Rolink stores it encrypted (AES-256-GCM) and decrypts it only to publish.

Without them, nothing is published and GetProject().events is false. Everything else keeps working. The setup is described in Set up Roblox.

The event topic is set per project in the same place: rolink by default, 1 to 80 letters, digits, - and _. The SDK’s topic option must match it.

Each event is one MessagingService message whose data is a JSON string. Every message has:

FieldValue
v1, the payload version. Messages with another version are ignored by the SDK.
typewallet_linked, wallet_unlinked, tx, watch or ping

The other fields depend on the type. null fields arrive as nil after JSONDecode. Ignore types and fields you do not know, so a newer payload never breaks an older game.

{ "v": 1, "type": "wallet_linked", "userId": 42, "address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
FieldTypeMeaning
userIdnumberThe player’s Roblox UserId.
addressstringThe wallet they linked.

Sent after every successful signature check on the project’s linking page: a first link, a swap to a new wallet, or the same wallet linked again.

SDK. The wallet cache for userId is set to address, then WalletLinked fires with (userId, address, player), where player is the Player if they are on this server.

{ "v": 1, "type": "wallet_unlinked", "userId": 42, "address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
FieldTypeMeaning
userIdnumberThe player who no longer has the wallet.
addressstringThe wallet they lost.

Sent in two cases:

  • the player unlinks on the linking page while a wallet is linked
  • another Roblox account proves ownership of the same wallet in this project. The wallet moves to the new account, and Rolink publishes wallet_unlinked for the previous owner and wallet_linked for the new owner. The two can arrive in either order.

A player who swaps their own wallet for a new one only triggers wallet_linked, with no wallet_unlinked for the old address.

SDK. If the cached wallet for userId is address, the cache entry is dropped. Then WalletUnlinked fires with (userId, address, player).

confirmed
{
"v": 1,
"type": "tx",
"key": "quest:42:intro",
"kind": "transfer-token",
"status": "confirmed",
"signature": "5z99PpyTfnCYHDAbyVoMFuHD3gWQFis2izi9NSJKEbijXWUQQnuWxcnAKUFxAhSJcJMExR9MfN69R1PhsvGiQYeu",
"userId": 42,
"error": null
}
failed
{
"v": 1,
"type": "tx",
"key": "daily:42:2026-10-08",
"kind": "transfer-sol",
"status": "failed",
"signature": "4huotLhmwu1MzxAgG1z8fhctptGdAsG3teAo1QPcxQkddj3jiGmxuK4WQydFK9j8gYUCDWNaSkMQePLdAGBbpEcf",
"userId": 42,
"error": "Transaction expired before it was confirmed"
}
FieldTypeMeaning
keystringThe request’s idempotencyKey.
kindstringtransfer-sol, transfer-token or mint-token.
statusstringconfirmed or failed. Intermediate statuses are never sent.
signaturestring or nullThe transaction signature. null when the transaction could not be built or signed.
userIdnumber or nullThe recipient player: the userId the request was sent to, or the player whose linked wallet was the recipient address at request time. Otherwise null.
errorstring or nullWhy it failed. null when confirmed.

Sent once per attempt, when it reaches a final status, on every server. That includes attempts whose HTTP answer already carried the final status. Delivery is still best effort, so call GetTransaction when an event does not arrive.

Outcomestatuserror
The RPC reports the signature confirmed or finalizedconfirmednull
The transaction landed but failed on-chainfailedThe on-chain error as JSON, such as {"InstructionError":[1,{"Custom":1}]}
The network rejected it in simulationfailedThe simulation error, in the coded form Solana error #-32002; Decode this error by running ...
It provably expired without landingfailedTransaction expired before it was confirmed
It could not be built (the request answered 502 rpc_error)failedCould not build the transaction: <reason>, with signature set to null
The managed wallet could not sign it (the request answered 502 wallet_unavailable)failedThe wallet provider’s message, with signature set to null

Not sent for requests refused by validation, the policy or a quota (nothing was stored), for intermediate pending or submitted states, or for requests marked failed with Abandoned before sending. A failed key that you retry starts a new attempt, which sends its own event.

SDK. TransactionUpdated fires with a TxUpdate, where key is renamed idempotencyKey:

{
idempotencyKey = "quest:42:intro",
kind = "transfer-token",
status = "confirmed",
signature = "5z99PpyT...",
userId = 42,
error = nil,
}
{
"v": 1,
"type": "watch",
"name": "treasury",
"address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
"signature": "3s8s4H43urMRvWTRaKLYepZ1BGbKMeMJ2R5xRTZMLgzAiNM3XqTqowHWPWaaeFon1dAesTQKvk25Xtr8JK9991rS",
"slot": 312345678,
"failed": false
}
FieldTypeMeaning
namestringThe watch name set in the dashboard.
addressstringThe watched address.
signaturestringThe new transaction’s signature.
slotnumberThe slot it landed in.
failedbooleantrue when the transaction failed on-chain.

Sent once for each new transaction that involves a watched address, at confirmed commitment, failed ones included:

  • Rolink polls each watched address every few seconds.
  • The first poll of a watch name only records where the address’s history ends, so past transactions are never replayed. An address with no history yet still gets its very first transaction delivered.
  • Each poll sends up to 1,000 new transactions per watch, oldest first. If more arrived since the last poll, the oldest ones beyond that are skipped.
  • If a poll fails, for example because the RPC is briefly unavailable, the next poll starts again from the same position.

The event names the transaction but does not describe it. Call GetSignatureStatus or your own indexer for details. See Watch addresses.

SDK. ChainActivity fires with { name, address, signature, slot, failed }.

{ "v": 1, "type": "ping", "at": 1791429403583 }
FieldTypeMeaning
atnumberWhen the test was sent, in Unix milliseconds.

Sent when you send a test event from Settings in the dashboard. It goes through Open Cloud with the project’s universe ID, key and topic, exactly like a real event, and the dashboard shows Roblox’s answer: accepted, or the HTTP status that explains what to fix. See Troubleshooting.

SDK. Ignored: no signal fires. To see it arrive in a live server, subscribe to the topic yourself, as shown below.

Rolink publishes with POST https://apis.roblox.com/cloud/v2/universes/<universe ID>:publishMessage, using the project’s Open Cloud key:

  • Each attempt times out after 5 seconds.
  • Network errors, 429 and 5xx answers are retried, up to 4 attempts, waiting 0.5, 1 and 2 seconds in between. After the last attempt the event is dropped.
  • Any other answer, such as 401 or 403 for a key without the right permission, is not retried, and the event is dropped.
  • Link and tx events are published in the background. A player linking a wallet, or a transaction request, never waits on Open Cloud.
  • Order is not guaranteed between events. Within one poll, watch events are sent oldest first.
  • With events turned off, the watcher still polls and records its position, so turning events on later does not replay what it already saw.
  • Changes to the universe ID, key or topic take effect within about 30 seconds.

MessagingService refuses messages over 1 kB, and Rolink measures the encoded JSON against 1,000 bytes. When a tx event with an error is over that size, its error is cut to its first 300 characters before sending. No other field is shortened.

The other events stay well under the limit: watch names are at most 32 characters, and addresses, signatures and numbers have bounded sizes.

If you do not use the SDK, subscribe to the topic yourself. Do not do both in one server, or each event is handled twice.

ServerScriptService/RolinkEvents.server.luau
local HttpService = game:GetService("HttpService")
local MessagingService = game:GetService("MessagingService")
MessagingService:SubscribeAsync("rolink", function(message)
local ok, event = pcall(HttpService.JSONDecode, HttpService, message.Data)
if not ok or type(event) ~= "table" or event.v ~= 1 then
return
end
if event.type == "wallet_linked" then
print("linked", event.userId, event.address)
elseif event.type == "tx" then
print("tx", event.key, event.status, event.signature or event.error)
elseif event.type == "ping" then
print("test event from the dashboard", event.at)
end
end)

SubscribeAsync can fail, so production code should wrap it in pcall and retry, as the SDK does.