Update path hash handling to accept variable-length hex-encoded hashes (e.g. "4a" for single-byte, "b3fa" for multibyte) instead of requiring exactly 2-character hashes. Bump meshcore dependency to >=2.3.0. - Update normalizer to accept even-length hex strings >= 2 chars - Update schemas and model docstrings for variable-length hashes - Add tests for multibyte and mixed-length path hash round-trips - Fix web test flakiness from local .env datetime locale leaking
14 KiB
MeshCore Event Schemas
This document defines the complete JSON payload schemas for all MeshCore events supported by the API.
Event Categories
Events are categorized by how they're handled:
- Persisted Events: Stored in database tables and available via REST API
- Webhook Events: Trigger HTTP POST notifications when configured
- Informational Events: Logged but not persisted to separate tables
Table of Contents
- Persisted & Webhook Events
- Persisted Events (Non-Webhook)
- Informational Events
- Webhook Payload Format
Persisted & Webhook Events
These events are both stored in the database and trigger webhooks when configured.
ADVERTISEMENT / NEW_ADVERT
Node advertisements announcing presence and metadata.
Database Table: advertisements
Payload Schema:
{
"public_key": "string (64 hex chars)",
"name": "string (optional)",
"adv_type": "string (optional)",
"flags": "integer (optional)",
"lat": "number (optional)",
"lon": "number (optional)"
}
Field Descriptions:
public_key: Node's full 64-character hexadecimal public key (required)name: Node name/alias (e.g., "Gateway-01", "Alice")adv_type: Node type - common values:"chat","repeater","room","companion"(other values may appear from upstream feeds and are normalized by the collector when possible)flags: Node capability/status flags (bitmask)lat: GPS latitude when provided by decoder metadatalon: GPS longitude when provided by decoder metadata
Example:
{
"public_key": "4767c2897c256df8d85a5fa090574284bfd15b92d47359741b0abd5098ed30c4",
"name": "Gateway-01",
"adv_type": "repeater",
"flags": 218,
"lat": 42.470001,
"lon": -71.330001
}
Webhook Trigger: Yes
REST API: GET /api/v1/advertisements
CONTACT_MSG_RECV
Direct/private messages between two nodes.
Database Table: messages
Payload Schema:
{
"pubkey_prefix": "string (12 chars)",
"path_len": "integer (optional)",
"txt_type": "integer (optional)",
"signature": "string (optional)",
"text": "string",
"SNR": "number (optional)",
"sender_timestamp": "integer (optional)"
}
Field Descriptions:
pubkey_prefix: First 12 characters of sender's public key (or source hash prefix in compatibility ingest modes)path_len: Number of hops message traveledtxt_type: Message type indicator (0=plain, 2=signed, etc.)signature: Message signature (8 hex chars) whentxt_type=2text: Message content (required)SNR: Signal-to-Noise Ratio in dBsender_timestamp: Unix timestamp when message was sent
Example:
{
"pubkey_prefix": "01ab2186c4d5",
"path_len": 3,
"txt_type": 0,
"signature": null,
"text": "Hello Bob!",
"SNR": 15.5,
"sender_timestamp": 1732820498
}
Webhook Trigger: Yes
REST API: GET /api/v1/messages
Webhook JSONPath Examples:
- Send only text:
$.data.text - Send text + SNR:
$.data.[text,SNR]
CHANNEL_MSG_RECV
Group/broadcast messages on specific channels.
Database Table: messages
Payload Schema:
{
"channel_idx": "integer (optional)",
"channel_name": "string (optional)",
"pubkey_prefix": "string (12 chars, optional)",
"path_len": "integer (optional)",
"txt_type": "integer (optional)",
"signature": "string (optional)",
"text": "string",
"SNR": "number (optional)",
"sender_timestamp": "integer (optional)"
}
Field Descriptions:
channel_idx: Channel number (0-255) when availablechannel_name: Channel display label (e.g.,"Public","#test") when availablepubkey_prefix: First 12 characters of sender's public key when availablepath_len: Number of hops message traveledtxt_type: Message type indicator (0=plain, 2=signed, etc.)signature: Message signature (8 hex chars) whentxt_type=2text: Message content (required)SNR: Signal-to-Noise Ratio in dBsender_timestamp: Unix timestamp when message was sent
Example:
{
"channel_idx": 4,
"path_len": 10,
"txt_type": 0,
"signature": null,
"text": "Hello from the mesh!",
"SNR": 8.5,
"sender_timestamp": 1732820498
}
Webhook Trigger: Yes
REST API: GET /api/v1/messages
Webhook JSONPath Examples:
- Send only text:
$.data.text - Send channel + text:
$.data.[channel_idx,text]
Compatibility ingest note:
- In LetsMesh upload compatibility mode, packet type
5is normalized toCHANNEL_MSG_RECVand packet types1,2, and7are normalized toCONTACT_MSG_RECVwhen decryptable text is available. - LetsMesh packets without decryptable message text are treated as informational
letsmesh_packetevents instead of message events. - For UI labels, known channel indexes are mapped (
17 -> Public,217 -> #test) and preferred over ambiguous/stale channel-name hints. - Additional channel labels can be provided through
COLLECTOR_LETSMESH_DECODER_KEYSusinglabel=hexentries. - When decoder output includes a human sender (
payload.decoded.decrypted.sender), message text is normalized toName: Message; sender identity remains unknown when only hash/prefix metadata is available.
Compatibility ingest note (advertisements):
- In LetsMesh upload compatibility mode,
statusfeed payloads are persisted as informationalletsmesh_statusevents and are not normalized toADVERTISEMENT. - In LetsMesh upload compatibility mode, decoded payload type
4is normalized toADVERTISEMENTwhen node identity metadata is present. - Payload type
4location metadata (appData.location.latitude/longitude) is mapped to nodelat/lonfor map rendering. - This keeps advertisement persistence aligned with native mode expectations (advertisement traffic only).
Compatibility ingest note (non-message structured events):
- Decoded payload type
9is normalized toTRACE_DATA(traceTag, flags, auth, path hashes, and SNR values). - Decoded payload type
11(Control/NodeDiscoverResp) is normalized tocontactevents for node upsert parity. - Decoded payload type
8is normalized to informationalPATH_UPDATEDevents (hop_count+ path hashes). - Decoded payload type
1can be normalized toTELEMETRY_RESPONSE,BATTERY,PATH_UPDATED, orSTATUS_RESPONSEwhen decrypted response content is structured and parseable.
Persisted Events (Non-Webhook)
These events are stored in the database but do not trigger webhooks.
TRACE_DATA
Network trace path results showing route and signal strength.
Database Table: trace_paths
Payload Schema:
{
"initiator_tag": "integer",
"path_len": "integer (optional)",
"flags": "integer (optional)",
"auth": "integer (optional)",
"path_hashes": "array of strings",
"snr_values": "array of numbers",
"hop_count": "integer (optional)"
}
Field Descriptions:
initiator_tag: Unique trace identifier (0-4294967295)path_len: Length of the pathflags: Trace flags/optionsauth: Authentication/validation datapath_hashes: Array of hex-encoded node hash identifiers, variable length (e.g.,"4a"for single-byte,"b3fa"for multibyte), ordered by hopssnr_values: Array of SNR values corresponding to each hophop_count: Total number of hops
Example:
{
"initiator_tag": 123456789,
"path_len": 3,
"flags": 0,
"auth": 1,
"path_hashes": ["4a", "b3fa", "02"],
"snr_values": [25.3, 18.7, 12.4],
"hop_count": 3
}
Note: MeshCore firmware v1.14+ supports multibyte path hashes. Older nodes use single-byte (2-character) hashes. Mixed-length hash arrays are expected in heterogeneous networks where nodes run different firmware versions.
Webhook Trigger: No
REST API: GET /api/v1/trace-paths
TELEMETRY_RESPONSE
Sensor data from network nodes (temperature, humidity, battery, etc.).
Database Table: telemetry
Payload Schema:
{
"node_public_key": "string (64 hex chars)",
"lpp_data": "bytes (optional)",
"parsed_data": "object (optional)"
}
Field Descriptions:
node_public_key: Full public key of the reporting nodelpp_data: Raw LPP-encoded sensor data (CayenneLPP format)parsed_data: Decoded sensor readings as key-value pairs
Example:
{
"node_public_key": "4767c2897c256df8d85a5fa090574284bfd15b92d47359741b0abd5098ed30c4",
"lpp_data": null,
"parsed_data": {
"temperature": 22.5,
"humidity": 65,
"battery": 3.8,
"pressure": 1013.25
}
}
Webhook Trigger: No
REST API: GET /api/v1/telemetry
CONTACTS
Contact sync event containing all known nodes.
Database Table: Updates nodes table
Payload Schema:
{
"contacts": [
{
"public_key": "string (64 hex chars)",
"name": "string (optional)",
"node_type": "string (optional)"
}
]
}
Field Descriptions:
contacts: Array of contact objectspublic_key: Node's full public keyname: Node name/aliasnode_type: One of:"chat","repeater","room","none"
Example:
{
"contacts": [
{
"public_key": "01ab2186c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1",
"name": "Alice",
"node_type": "chat"
},
{
"public_key": "b3f4e5d6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4",
"name": "Bob",
"node_type": "chat"
}
]
}
Webhook Trigger: No
REST API: GET /api/v1/nodes
Informational Events
These events are logged to events_log table but not persisted to separate tables.
SEND_CONFIRMED
Confirmation that a sent message was delivered.
Payload Schema:
{
"destination_public_key": "string (64 hex chars)",
"round_trip_ms": "integer"
}
Field Descriptions:
destination_public_key: Recipient's full public keyround_trip_ms: Round-trip time in milliseconds
Example:
{
"destination_public_key": "4767c2897c256df8d85a5fa090574284bfd15b92d47359741b0abd5098ed30c4",
"round_trip_ms": 2500
}
STATUS_RESPONSE
Device status information.
Payload Schema:
{
"node_public_key": "string (64 hex chars)",
"status": "string (optional)",
"uptime": "integer (optional)",
"message_count": "integer (optional)"
}
Field Descriptions:
node_public_key: Node's full public keystatus: Status descriptionuptime: Uptime in secondsmessage_count: Total messages processed
Example:
{
"node_public_key": "4767c2897c256df8d85a5fa090574284bfd15b92d47359741b0abd5098ed30c4",
"status": "operational",
"uptime": 86400,
"message_count": 1523
}
BATTERY
Battery status information.
Payload Schema:
{
"battery_voltage": "number",
"battery_percentage": "integer"
}
Field Descriptions:
battery_voltage: Battery voltage (e.g., 3.7V)battery_percentage: Battery level 0-100%
Example:
{
"battery_voltage": 3.8,
"battery_percentage": 75
}
PATH_UPDATED
Notification that routing path to a node has changed.
Payload Schema:
{
"node_public_key": "string (64 hex chars)",
"hop_count": "integer"
}
Field Descriptions:
node_public_key: Target node's full public keyhop_count: Number of hops in new path
Example:
{
"node_public_key": "4767c2897c256df8d85a5fa090574284bfd15b92d47359741b0abd5098ed30c4",
"hop_count": 3
}
Other Informational Events
The following events are logged but have varying or device-specific payloads:
- STATISTICS: Network statistics (varies by implementation)
- DEVICE_INFO: Device hardware/firmware information
- BINARY_RESPONSE: Binary data responses
- CONTROL_DATA: Control/command responses
- RAW_DATA: Raw protocol data
- NEXT_CONTACT: Contact enumeration progress
- ERROR: Error messages with description
- MESSAGES_WAITING: Notification of queued messages
- NO_MORE_MSGS: End of message queue
- RX_LOG_DATA: Receive log data
Webhook Payload Format
All webhook events are wrapped in a standard envelope before being sent:
{
"event_type": "string",
"timestamp": "string (ISO 8601)",
"data": {
// Event-specific payload (see schemas above)
}
}
Example (Channel Message):
{
"event_type": "CHANNEL_MSG_RECV",
"timestamp": "2025-11-28T19:41:38.748379Z",
"data": {
"channel_idx": 4,
"path_len": 10,
"txt_type": 0,
"text": "Hello from the mesh!",
"SNR": 8.5,
"sender_timestamp": 1732820498
}
}
JSONPath Filtering
You can filter webhook payloads using JSONPath expressions:
| JSONPath | Result |
|---|---|
$ |
Full payload (default) |
$.data |
Event data only |
$.data.text |
Message text only |
$.data.[text,SNR] |
Multiple fields as array |
$.event_type |
Event type string |
$.timestamp |
Timestamp string |
See AGENTS.md for webhook configuration details.
Event Flow
- Hardware/Mock MeshCore → Generates raw events
- Event Handler → Processes and persists to database
- Webhook Handler → Sends HTTP POST to configured URLs (if enabled)
- REST API → Query historical data from database
Most events are logged to the events_log table with full payloads for debugging and audit purposes. Some high-frequency informational events (e.g., NEXT_CONTACT) are intentionally excluded from logging to reduce database size.