Skip to content

Server Events

The phone announces its lifecycle moments as plain server-local events. Listen with a standard handler:

lua
AddEventHandler('sd-phone:server:messages:sent', function(message)
    -- react to the send
end)

All events fire via TriggerEvent, which is synchronous: handlers run inline inside the phone's own action, so keep them cheap and never let them error. Payloads carry citizenids where noted; they are for trusted server resources and must not be forwarded to clients unfiltered.

Phone Numbers

sd-phone:server:number:assigned

Fires exactly once per character, when their phone number is first minted.

lua
AddEventHandler('sd-phone:server:number:assigned', function(citizenid, number)
    print(citizenid .. ' was assigned ' .. number)
end)
ParameterTypeDescription
citizenidstringThe character the number was minted for
numberstringThe raw-digit number

INFO

No source argument: minting can happen with the owner offline (the getPhoneNumberByIdentifier export with ensure = true). Resolve a server id yourself when you need one, and expect nil.


Messages

sd-phone:server:messages:sent

Fires once per logical SMS send, on all three delivery paths: a player's 1:1 send, a player's group send, and a system text.

lua
AddEventHandler('sd-phone:server:messages:sent', function(m)
    if not m.system and not m.group then
        print(m.senderNumber .. ' texted ' .. m.targetNumber)
    end
end)
ParameterTypeDescription
systembooleantrue for sendSystemMessage-style service texts
groupbooleantrue for group-thread sends
sourcenumber|nilSending player's server id (absent on system texts)
citizenidstring|nilSending character (absent on system texts)
senderNumberstringSending number
senderNamestring|nilDisplay name (system texts only)
targetNumberstring|nilRecipient number (1:1 and system)
targetCitizenidstring|nilRecipient character, nil when the number is not in service
targetSourcenumber|nilRecipient's server id when online and delivered live
groupIdnumber|nilGroup thread id (group sends)
memberstable|nilGroup roster of { citizenid, number, name } (group sends); treat as read-only
kindstringMessage kind (text, image, gif, location, ...)
bodystringMessage text
metatable|nilSanitized kind metadata
midstringShared logical id correlating the per-mailbox copies
messageIdnumberStored row id (sender copy for player sends, recipient copy for system texts)
recipientIdnumber|nilRecipient copy row id, nil when no copy was stored
withheldboolean|niltrue when airplane mode is holding the delivery
timestampnumberUnix seconds

INFO

On a 1:1 send, targetCitizenid can be set while recipientId and targetSource are nil: the number is in service but the sender is blocked, and the block is deliberately indistinguishable from offline.


Mail

sd-phone:server:mail:sent

Fires once per mail send, both player composes and system mail.

lua
AddEventHandler('sd-phone:server:mail:sent', function(mail)
    print(('%s mailed %d recipient(s): %s'):format(mail.from.email, #mail.to, mail.subject))
end)
ParameterTypeDescription
systembooleantrue for sendMail-style system senders
idstring|nilStored message id (absent on a system send that reached nobody)
citizenidstring|nilSending character (player composes only)
fromtable{ name, email }
tostring[]Normalized recipient addresses
subjectstringSubject line
bodystringBody text
sentAtnumberUnix seconds
deliverednumber|nilRecipient accounts that existed (system sends only)

Calls

All call events share one call table:

FieldTypeDescription
channelnumberThe pma-voice call channel, doubling as the call id
companystring|nilDisplay name for company/group rings, nil for 1:1 calls
callertable{ source, citizenid, name, number }
calleetable|nilSame shape; nil on group rings before an answer
targetstable|nilGroup rings only: everyone rung, same party shape

sd-phone:server:call:started

Fires when a 1:1 call starts ringing, and once per company/group ring (with targets populated and callee nil).

lua
AddEventHandler('sd-phone:server:call:started', function(call)
    print(call.caller.number .. ' is calling ' .. (call.callee and call.callee.number or call.company))
end)

sd-phone:server:call:answered

Fires when a call is answered, including a group ring being promoted to a live call (the answering employee becomes callee; company keeps the display name). The payload adds startedAt (unix seconds at answer).

sd-phone:server:call:ended

Fires on every teardown, answered or not, with endedBy as a second argument.

lua
AddEventHandler('sd-phone:server:call:ended', function(call, endedBy)
    if call.answered then
        print(('call on channel %d lasted %ds'):format(call.channel, call.duration))
    end
end)

Extra payload fields on this event:

FieldTypeDescription
answeredbooleanWhether the call was ever active
durationnumberSeconds, 0 when never answered
reasonstring'declined', 'hangup', 'disconnected', or 'unavailable'
ParameterTypeDescription
calltableThe call table above
endedBynumber|nilServer id that caused the teardown, nil on disconnects

INFO

Cancelled group rings that nobody answered also fire this event (answered = false, callee = nil), so a dispatch script can observe missed company calls. Payloads are built from stored session state, so a disconnecting player still appears with the data they rang with.


Services

sd-phone:server:services:message

Fires when a citizen messages a company through the Services app (or the messageCompany export).

lua
AddEventHandler('sd-phone:server:services:message', function(e)
    if e.job == 'police' then
        print(('911 text from %s (%s): %s'):format(e.name, e.number, e.body))
    end
end)
ParameterTypeDescription
sourcenumberSending player's server id
citizenidstringSending character
jobstringCompany job name
labelstringCompany display label
numberstringSender's phone number
namestringSender's display name
kindstringComposer kind
bodystringMessage text
metatable|nilKind metadata

Banking

sd-phone:server:banking:transaction

Fires when an externally-logged Wallet row is appended (the addBankTransaction export or the internal event that backs it).

ParameterTypeDescription
citizenidstringRow owner
sourcenumber|nilOwner's server id when online
amountnumberSigned amount, positive = money in
labelstringRow label
categorystring|nilCategory tag
counterpartystring|nilWho the money moved to or from
timestampnumberUnix seconds

sd-phone:server:banking:transfer

Fires once per player-to-player Wallet transfer made in the phone.

ParameterTypeDescription
fromCitizenidstringSender
fromNumberstringSender's number
fromSourcenumberSender's server id
toCitizenidstringRecipient
toNumberstringRecipient's number
toSourcenumber|nilRecipient's server id when online
amountnumberAmount moved
notestring|nilTransfer note
timestampnumberUnix seconds

Photos

sd-phone:server:photos:added

Fires when a photo lands in a gallery, on every add path: camera captures, saved URLs, and the addPhoto export.

ParameterTypeDescription
sourcenumberOwner's server id
citizenidstringOwner
idnumberGallery row id
urlstringHosted media URL

sd-phone:server:photos:deleted

Fires when a player deletes media from their own gallery. Retention pruning and admin wipes bypass it deliberately.

ParameterTypeDescription
sourcenumberOwner's server id
citizenidstringOwner
idnumberDeleted row id
urlstringThe deleted media URL

Social

sd-phone:server:birdy:post

Fires when a new Birdy post is created.

ParameterTypeDescription
idnumberPost id
sourcenumberAuthor's server id
citizenidstringAuthor
usernamestringAuthor's handle
displayNamestringAuthor's display name
bodystringPost text
imagesstring[]|nilAttached image URLs, nil for text-only posts

sd-phone:server:photogram:post

Fires when a new Photogram post is created.

ParameterTypeDescription
idnumberPost id
sourcenumberAuthor's server id
citizenidstringAuthor
usernamestringAuthor's handle
imagesstring[]Post media URLs
captionstringCaption (may be empty)
locationstring|nilLocation tag
privatebooleanWhether the author's account is private

INFO

Private authors' posts are scoped to approved followers inside the app. This payload is server-local and trusted; never forward it to clients unfiltered.


Classifieds

sd-phone:server:pages:post

Fires when a new Pages listing is posted. Payload fields come from the stored row: { id, source, citizenid, number, title, body, price, image, images } (images is the stored JSON string).

sd-phone:server:marketplace:post

Fires when a new Marketplace listing is posted. Same shape as the Pages event.


Contacts

sd-phone:server:contacts:added

Fires when a contact lands in a player's phone book, from the app, the addContact export, or an accepted AirShare.

ParameterTypeDescription
sourcenumberOwner's server id
citizenidstringOwner
idnumberContact row id
namestringContact name
phonestringContact number, digit-normalized
sharedbooleantrue when it arrived via AirShare

sd-phone:server:contacts:removed

Fires when contacts are removed, from the app delete or the removeContactByNumber export.

ParameterTypeDescription
sourcenumberOwner's server id
citizenidstringOwner
idnumber|nilRow id (app deletes)
phonestringRemoved number, digit-normalized
removednumberRows removed

Wi-Fi

sd-phone:server:wifi:connected

Fires when a player joins a Wi-Fi network, once the join has been recorded. The password has already been checked and the server has already placed the player inside the network's radius, so this is a connection that genuinely happened.

lua
AddEventHandler('sd-phone:server:wifi:connected', function(source, networkId)
    if networkId == 'mazebank' then
        print(GetPlayerName(source) .. ' is on the bank router')
    end
end)
ParameterTypeDescription
sourcenumberThe player's server id
networkIdstringNetwork id from configs/wifi.lua

sd-phone:server:wifi:disconnected

Fires when a connection ends, once per ending.

ParameterTypeDescription
sourcenumberThe player's server id
networkIdstringThe network they were on, read before the connection was cleared

Every path that ends a connection fires it: an explicit disconnect, walking out of range, the network being edited out of the config under a live connection, and the player dropping from the server. A listener gating a door or a terminal on a network has to hear the ending rather than merely stop being told about the id, so none of those four is allowed to end a connection silently.


Bluetooth

A device's own onConnect and onDisconnect callbacks only reach the resource that registered it. These events are how everything else watches: a logging script, an admin tool, or a resource that cares whether a player has any audio device at all without owning one.

sd-phone:server:bluetooth:connected

Fires when a player connects to a device, after the owning script's onConnect has run, so a listener always sees a settled connection.

lua
AddEventHandler('sd-phone:server:bluetooth:connected', function(source, deviceId, device)
    print(('%s connected to %s (%s)'):format(GetPlayerName(source), device.name, device.owner))
end)
ParameterTypeDescription
sourcenumberThe player's server id
deviceIdstringDevice id as its owning resource registered it
devicetable{ id, name, kind, owner }, identity only

Both the pairing a player taps in Settings and the reconnect sweep that picks a device back up when they walk into range come through here. There is no separate "paired" event, because pairing always ends in a connection.

sd-phone:server:bluetooth:disconnected

Fires when a connection ends, once per ending.

lua
AddEventHandler('sd-phone:server:bluetooth:disconnected', function(source, deviceId, reason)
    if reason == 'range' then
        print(GetPlayerName(source) .. ' walked away from ' .. deviceId)
    end
end)
ParameterTypeDescription
sourcenumberThe player's server id
deviceIdstringThe device they were on, read before the connection was cleared
reasonstringWhy it ended, from the table below
ReasonMeaning
manualThe player disconnected or forgot the device in Settings
rangeThey walked out of the device's reach
disabledThey switched their Bluetooth radio off
droppedThey left the server
unregisteredThe device was removed, or the resource owning it stopped
kickedA script called disconnectBluetooth

Every path that ends a connection fires it, including the two that no client ever reports: the owning resource stopping, and the player dropping. A listener holding state against a connection has to hear the ending rather than merely stop being told about it, so none of the six is allowed to end a connection silently.


Cell service

sd-phone:server:service:lost

Fires when a player loses cell service.

ParameterTypeDescription
sourcenumberThe player's server id

INFO

A client only reports which edge it crossed. The level is re-derived from the server's own coords before anything fires, so a forged report cannot announce a loss that did not happen. A repeat loss announces nothing.

sd-phone:server:service:regained

Fires when a player comes back into coverage.

ParameterTypeDescription
sourcenumberThe player's server id

Pre-existing rather than new: this is the event that drains texts withheld while the player was out of coverage.


Settings

sd-phone:server✈️released

Fires when a player switches airplane mode off, releasing withheld messages.

ParameterTypeDescription
sourcenumberThe player's server id

sd-phone:server:documents:created

Fires once per document created through the Documents exports — player-created documents do not fire it.

lua
AddEventHandler('sd-phone:server:documents:created', function(data)
    print(('%s issued "%s" (%s) as %s'):format(data.resource or '?', data.name, data.kind, data.docId))
end)
FieldTypeDescription
citizenidstringThe owning identity (the acting phone profile under unique phones)
docIdstringThe new document's id
namestringDocument display name
kindstring'text', 'image', or 'file'
resourcestring?The resource that invoked the export

TIP

Client-local events (phone opened, camera mode, flashlight) live on the Client Events page. Third-party scripts written against lb-phone's events keep working through the lb-phone compatibility layer.