Pingtower Docs
Docs The addons agent websocket

websocket

Open a WebSocket, optionally send one text message, and check the first reply. The upgrade and the reply are timed apart.

Realtime features fail apart from the HTTP API beside them: a proxy that drops the Upgrade header, or a socket server that accepts and then never answers. This check performs the upgrade itself and verifies the server’s answer. It then sends your message, if you set one, only after the server has proved it speaks WebSocket, and reads the reply.

It runs on your own agents and on Pingtower’s servers as a hosted check.

Parameters

ParameterDefaultMeaning
urlREQUIREDA ws:// or wss:// URL. The port defaults to 80 or 443. Credentials in the URL are refused.
sendA text message to send once connected, at most 4 KiB.
expectText the first message received must contain. Set without send, the check waits for the server to speak first.
subprotocolA subprotocol to request. The server must accept it.
headersMap of extra headers for the upgrade request. Host, Upgrade, Connection and any Sec-WebSocket-* header are refused, as is a line break in a value. A value can take a sealed secret as {{NAME}}: see Secrets.
max_responseReply time above this is degraded. With neither send nor expect, the upgrade time instead.

wss:// uses TLS 1.2 or later and verifies the certificate.

Status

With neither send nor expect, a verified upgrade is up: upgraded in 85ms. Otherwise the first data message must arrive and contain expect: replied in 40ms. The check answers the server’s pings while it waits. Over max_response, either one is degraded.

It is down when:

  • the host does not resolve, the connection is refused or times out, or the TLS handshake fails;
  • the upgrade is refused, such as upgrade refused with HTTP 403, or the server’s answer is not a valid WebSocket upgrade;
  • the server does not accept the requested subprotocol;
  • no message arrives before the timeout, or the server closes the connection first;
  • the reply does not contain expect, or is larger than 64 KiB.

Measurements

KeyMeaning
dns_msResolution time, when the URL names a host rather than an IP
connect_msTCP connect time
handshake_msTLS, when wss://, and the upgrade
response_msFrom sending send, or from the upgrade, to the first reply, when the check waits for one
total_msThe whole run
status_codeLabel: the HTTP status, when the upgrade was refused

The check needs addons agent 0.4.0 or later; an older agent reports it as an unknown addon.

Example

- id: live-feed
  addon: websocket
  interval: 60s
  with:
    url: wss://live.acme.example/socket
    send: '{"type":"ping"}'
    expect: '"type":"pong"'
    headers:
      Origin: https://acme.example
    max_response: 500ms

Secrets

A header value written as it is, such as Authorization: Bearer abc123, is stored in the clear. Everyone who can read checks in the project sees it, in the web app, the apps and the API. A create or update that finds a credential in one still saves, and the answer carries secret_warnings naming the header.

To keep a credential sealed, put it in the check’s env and reference it from the header value as {{NAME}}:

{"addon":"websocket",
 "with":{"url":"wss://live.acme.example/socket",
         "headers":{"Authorization":"Bearer {{TOKEN}}"}},
 "env":{"TOKEN":"…"}}

The agent fills in {{TOKEN}} when the check runs. The value is sealed at rest and write-only: reading the check back shows env_keys and the header as written, Bearer {{TOKEN}}, never the value. A value of env that turns up in a result is stored as [redacted].

  • A name is upper-case letters, digits and _, not starting with a digit, at most 64 characters. A check takes at most 20.
  • A value is 1 to 4096 bytes, has no line break or NUL, and does not start with enc:.
  • Every {{NAME}} in a header value must be in env, and every name in env must be used by a header value. A header value may hold several references, with other text around them.
  • While the check has env, every {{ in a header value must begin a {{NAME}} reference, and {{ is refused in url, send, expect and subprotocol. A check without env is sent exactly as written, braces included.
  • A value is filled in once: a secret that itself contains {{…}} is sent as it is.

A create, or an update that sends env, that breaks these is refused with a 400, such as with.headers Authorization references {{TOKEN}}, which env does not define or env TOKEN is not referenced by any with.headers value.

The secret is tied to the check’s url. Leaving env out of an update keeps it while url stays exactly the same, host, path and query. Otherwise the update must send env again, or {} to clear it, and is refused with a 422:

env: with.url now points at another destination; re-supply env (or send {} to clear it)

An update that leaves env out is refused the same way when its headers no longer fit the stored names: env: with.headers references NAME, which is not sealed; send env with the check or env: NAME is no longer referenced by any with.headers value; re-supply env (or send {} to clear it). Adding a region or source to sources without sending env again is refused too. Minting a new agent token for a source stops the check running from that source until you send env again: see Secrets.

Sealed headers work only on checks Pingtower assigns to the agent, not on checks in a local agent.yaml, and need addons agent 0.6.0 or later. An agent older than 0.6.0 refuses a WebSocket check that carries env and runs the rest of its checks; the refused check goes silent there, and the agent’s heartbeat counts it in assignments_rejected.

A rule for it

{"name":"socket-down","match":{"tags":["addon:websocket"],"min_level":400},
 "fire_after":{"count":2,"window_minutes":5,"distinct_sources":true}}
Last updated 11 Oct 2026 Report a problem with this page