Transport Types

Robots receive events through one of three transport mechanisms. Choose the one that best fits your infrastructure and reliability requirements.

Webhook

Lag sends an HTTP POST request to your configured URL each time an event occurs. This is the most common transport for production integrations.

How It Works

  1. An event occurs in your server (message sent, user joins, etc.)
  2. Lag constructs a JSON payload with the event data
  3. Lag sends an HTTP POST to your webhook URL
  4. Your server processes the event and responds with a 2xx status code

Request Format

Each webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
webhook-idUnique identifier for this delivery attempt
webhook-timestampUnix timestamp (seconds) when the event was sent
webhook-signatureHMAC-SHA256 signature for verification
User-AgentLag-Webhooks/1.0

The request body contains the event payload as JSON.

Signature Verification

Webhook requests are signed following the Standard Webhooks specification. The signature uses HMAC-SHA256 with your webhook secret.

To verify a signature:

  1. Decode the base64 portion of your whsec_ prefixed secret
  2. Construct the signed content: {webhook-id}.{webhook-timestamp}.{body}
  3. Compute HMAC-SHA256 of the signed content using the decoded secret
  4. Base64-encode the result and compare with the signature in the header

The webhook-signature header contains one or more signatures prefixed with v1,. Match against any of them (multiple signatures appear during secret rotation).

Retry Logic

If your endpoint does not respond with a 2xx status within 15 seconds, Lag retries the delivery. The retry schedule uses exponential backoff:

AttemptDelay
1Immediate
25 seconds
325 seconds
42 minutes
510 minutes
630 minutes
71 hour
83 hours
98 hours
1024 hours

After 10 failed attempts, the event is marked as failed.

If your endpoint responds with 410 Gone, Lag immediately disables the robot to prevent further delivery attempts. This is useful for intentionally decommissioning a webhook endpoint.

Requirements

  • Your URL must use HTTPS
  • Your server must respond within 15 seconds
  • Return a 2xx status code to acknowledge receipt
  • Verify signatures to ensure requests are from Lag

SSE (Server-Sent Events)

Your application maintains a persistent HTTP connection to Lag and receives events as a real-time stream.

How It Works

  1. Your application sends a GET request to /robots/@me/events/sse
  2. Lag holds the connection open and sends events as they occur
  3. Events arrive as standard SSE text/event-stream data
  4. Your client processes each event as it arrives

Connection Setup

GET /robots/@me/events/sse
Authorization: Robot lag_robot_<prefix>_<secret>
Accept: text/event-stream

The response is a text/event-stream with events formatted as:

id: evt_01H1234567890
event: room.message
data: {"type":"room.message","serverId":"...","data":{...}}

Keepalive

Lag sends a keepalive comment (:keepalive) every 30 seconds to prevent proxy timeouts. If you do not receive any data (events or keepalives) for more than 60 seconds, your connection has likely been dropped and should be re-established.

Replay with Last-Event-ID

If your connection drops, you can resume from where you left off by including the Last-Event-ID header with the ID of the last event you received:

GET /robots/@me/events/sse
Authorization: Robot lag_robot_<prefix>_<secret>
Last-Event-ID: evt_01H1234567890

Lag will replay any events that occurred after the specified ID.

Event Delivery

Events are marked as delivered as soon as they are sent over the SSE connection. There is no acknowledgment mechanism - if your client crashes after receiving but before processing an event, that event will not be redelivered unless you reconnect with the appropriate Last-Event-ID.

Long-Poll

Your application periodically requests pending events from Lag. This is the simplest transport to implement and works well behind restrictive firewalls.

How It Works

  1. Your application sends a GET request to /robots/@me/events/poll
  2. If events are pending, Lag returns them immediately
  3. If no events are pending, Lag holds the connection for up to 30 seconds
  4. After 30 seconds with no events, Lag returns an empty array
  5. Your application immediately sends another request

Request Format

GET /robots/@me/events/poll
Authorization: Robot lag_robot_<prefix>_<secret>

Response Format

{
  "events": [
    {
      "id": "evt_01H1234567890",
      "type": "room.message",
      "timestamp": "2024-01-15T10:30:00Z",
      "data": { ... }
    }
  ]
}

Each poll returns up to 50 events at a time. If more events are pending, poll again immediately to retrieve the next batch.

An empty response looks like:

{
  "events": []
}

Delivery Semantics

Events are marked as delivered once they are included in a poll response. Poll immediately after processing to minimize latency.

Comparison

FeatureWebhookSSELong-Poll
LatencyLow (push)Lowest (stream)Medium (up to poll interval)
ImplementationMediumMediumSimple
Firewall-friendlyRequires inbound HTTPSOutbound onlyOutbound only
ReliabilityHigh (retries)Medium (reconnect needed)Medium (poll on failure)
Server requiredYes (public HTTPS)NoNo
OrderingGuaranteed per eventGuaranteedGuaranteed
Best forProduction servicesReal-time botsSimple integrations