Receive real-time HTTP POST notifications for daemon events.
Overview
When configured, the daemon POSTs a JSON event to a webhook URL for events such as connections, trust changes, messages received, and pub/sub activity. Events are delivered asynchronously and non-blocking. Delivery is buffered in a 1024-event channel, and each event gets 3 delivery attempts total with a 1s doubling backoff between attempts. Events are dropped immediately, with zero attempts, when the 1024-event buffer is full. While the circuit breaker is open (it opens after 5 consecutive failures and cools down for 30s), events are skipped. There is no durable or redelivery queue.
This command persists the setting to ~/.pilot/config.json and applies it immediately to the running daemon. It returns `webhook` and `applied` (boolean).
To clear a webhook:
pilotctl clear-webhook
This command removes the webhook URL from the configuration and the running daemon. It returns `webhook` and `applied` (boolean).
The webhook URL can also be set in ~/.pilot/config.json:
daemon.shutting_down: Daemon is shutting down (final pre-exit event)
node.registered: Daemon registered with the registry
node.reregistered: Re-registration after keepalive timeout
agent.registered: The daemon registered (or re-registered) itself with the registry
agent.heartbeat: The daemon's own periodic liveness heartbeat
key.rotated: Daemon's Ed25519 keypair was rotated
network.auto_joined: Daemon auto-joined a network on startup
Connection events:
conn.syn_received: Incoming connection request
conn.established: Connection fully established
conn.fin: Connection closed gracefully (FIN)
conn.rst: Connection reset
conn.idle_timeout: Connection timed out due to inactivity
conn.dead_peer: Keepalive probes failed; peer declared dead
conn.accept_queue_full: Accept queue overflowed — connection dropped before processing
syn.rejected: Incoming SYN rejected (untrusted source)
syn.port_rejected: Incoming SYN rejected by network policy
Tunnel events:
tunnel.peer_added: New tunnel peer discovered
tunnel.established: Encrypted tunnel to a peer established - carries `peer_node_id`, `authenticated`, `relay`, and `rekeyed` (true when it followed a key rotation)
tunnel.rekey_gave_up: Key-exchange retransmit to `peer_node_id` gave up after the maximum rekey attempts
tunnel.relay_activated: Relay fallback activated for a peer (symmetric NAT)
tunnel.desync_salvage: Tunnel key desync detected and salvaged via re-handshake
tunnel.rx_recovered: Inbound path recovered after a watchdog soft recovery
tunnel.rx_wedged_exit: Inbound path wedged past soft recovery — daemon exiting (code 86) for supervisor respawn
tunnel.rx_wedge_restart_loop: Restart-loop circuit breaker opened — repeated wedge exits within the window; daemon stays up and keeps soft-recovering instead of exiting
tunnel.path_suspect: Per-peer path watchdog: a peer stayed inbound-silent past the probe budget — its path is being reset in place (prefer-direct sequence)
tunnel.path_recovered: Per-peer path watchdog: a probed peer's inbound traffic resumed — suspicion cleared without a reset
Trust & handshake events:
handshake.received: Trust handshake request received from a peer
trust.changed: Trust state for a peer changed - carries `peer_node_id`, `state` (e.g. `granted`) and a `reason` (`mutual`, `same_network`, or `trusted_agent`)
trust.revoked: Trust revoked locally (you untrusted a peer)
trust.revoked_by_peer: Trust revoked by a remote peer
Data events:
message.received: Typed message received via data exchange (port 1001)
file.received: File received via data exchange (port 1001) — buffered into `~/.pilot/received/`
security.src_spoofed: A frame was dropped because its claimed source node did not match the authenticated peer - carries `authenticated_peer` and `claimed_src`
Policy & Managed events:
policy.cycle: Policy evaluation cycle completed
policy.eval_error: A programmable-policy expression failed to evaluate (fail-closed: gate denied / directives skipped) - carries `network_id`, `event`, and `error`
policy.prune_trust: A policy cycle pruned low-scoring trust links - carries `network_id`, `rule`, and `pruned` (count)
policy.fill_trust: A policy cycle sent trust requests to fill network vacancies - carries `network_id`, `rule`, and `sent` (count)
policy.join_denied: A policy denied a peer's presence and evicted it locally - carries `network_id` and `peer_id`
network.joined: Membership reconciliation observed a newly joined network - carries `network_id`, plus `rules`, `expr_policy`, and `member_tags` when present
network.left: Membership reconciliation observed a network this node is no longer a member of - carries `network_id`
network.tags_changed: This node's authoritative member tags changed within a network - carries `network_id` and the new `tags` list
network.policy_load_failed: A network's port policy could not be fetched from the registry — the last-known policy is retained (fail-closed) - carries `net_id` and `had_prior`
Payload format
Every webhook POST contains a JSON body with this structure: