Skip to content

Event export

While a team records activity, its owners can send the team’s events on to a log system of their own, such as Splunk, Datadog, Elastic or Microsoft Sentinel, or any webhook that takes JSON over HTTPS. Every change to the team’s record goes, and so does what members do with the vaults’ items. Each event is signed, so the receiver can check that it came from the team and was not changed on the way. Everyone in the team is told while it is on.

ZestSSH Cloud sends nothing. It cannot read the activity, which is sealed with the team’s key, and it never learns where the events go. One device of the owner who set it up sends them, straight to the address that owner gave, while ZestSSH runs on that device.

  • You are an owner. Everyone else sees Only owners set up event export.
  • Activity is on. Event export sends only while the team records activity. Until then the panel says Turn on Activity above first. There is nothing to send while the team records no activity. See Trash, history and activity.
  • Pick the device that sends. Events flow only while ZestSSH runs on it. The address, the header and the signing key stay in that device’s secure storage, in no sync and no backup. With Encrypt data at rest on, they are also sealed under the key your app lock PIN protects, so they open only while the app is unlocked.
  1. On the device that is to send, open Team, then Rules, and find Event export.
  2. Choose Set up.
  3. Under Address, enter where your log system takes events over HTTPS, for example https://logs.example.com/ingest. Plain http:// is taken only for an address on your own network, such as http://192.168.10.24/ingest.
  4. Under Format, choose One event per line, which most log systems take, or A JSON array, which Datadog and webhooks take.
  5. Under Header (optional), give the name and value of one header your log system asks for before it takes events, a token most often. The value stays hidden as you type it.
  6. Choose Send a test event. The form says whether the receiver took it, for example The receiver took the test event (200).
  7. Choose Turn on. Confirm Send Example Ops’s events on? with Turn on, and sign in again if asked.
  8. The key events are signed with shows the key. Choose Copy the key and give it to whoever runs the receiving system. It is in the team’s record too.

From then on the section shows On, says This device sends the events while ZestSSH runs on it. and where they go (“To logs.example.com”), and its Delivery line says how it goes, for example “Last sent Oct 18, 2026 9:05 AM. 120 events so far.”

  • Every change to the team’s record, starting with the entry that turned sending on: members added, suspended, resumed or removed, roles, new vaults and their settings, new keys, rules, team recovery, certificate authorities, what the team records, access given until a set time, approved devices, and event export itself.
  • Every note of activity members’ devices send while activity is on: opening a key’s secrets, copying or exporting a private key, connecting to a host. A note that cannot be opened, or that names someone other than the account that sent it, goes as an event of its own (unreadable), so nothing a device sent is left out.
  • A test event each time you send one. It says that nothing happened.

Events name people by their address, and vaults, roles and items by their names, so the receiver reads names and not only ids.

What is never sent: a password, a key, a certificate, or anything a session showed. Nothing from before you turned it on. Team session logs and version history stay in ZestSSH.

  • One POST to the address for up to 100 events. One event per line is sent as application/x-ndjson; A JSON array as application/json.
  • Your header goes with each request. Redirects are not followed, since the header is for the address you gave and nowhere else.
  • Each request waits up to 20 seconds. A 2xx answer means the receiver took the events. Of the answer, only its status is kept.
  • The device sends after each sync and once a minute, up to 10 requests in each round. What is left goes with the next round, in order.
  • When a request is not taken, the section shows Failing, and a box Events are not getting through says why and since when, for example “Not sent since Oct 18, 2026 9:05 AM: the receiver answered 503. It is tried again every minute, and nothing is skipped.”, with Copy details for whoever runs the receiver. Other reasons it gives are the receiver did not answer in time, the receiver’s certificate was not accepted, the address could not be reached and the request could not be made.
  • An event can arrive twice, with the same id, but never fails to arrive because another did. A receiver that wants each event once keeps the ids it has seen.

A record event, sent when an owner suspended a member (made-up values):

{"action":"member.suspend","actor":{"email":"alex@example.com","uid":"..."},"change":"member_suspended","id":"record-41","key":"<base64 public key>","message":"alex@example.com: Suspended priya@example.com","orchard":{"id":"...","name":"Example Ops"},"record":{"hash":"<hex>","prev":"<hex>","seq":41},"sig":"<base64 signature>","source":"zestssh","target":{"email":"priya@example.com","uid":"..."},"time":"2026-10-02T12:05:00.000Z","time_source":"seen","type":"record","v":1}
FieldWhat it holds
v1, the version of the event’s layout.
sourcezestssh.
orchardThe team’s id, and its name.
idrecord-<number>, activity-<number> or test-<milliseconds>. The same event always has the same id.
typerecord, activity or test.
actionFor a record event, the entry’s type, such as member.suspend. For activity: reveal, copy, export, connect or unreadable. For a test: test.
changeRecord events: what changed, such as member_suspended.
timeUTC, to the millisecond.
time_sourceWhose clock time is: seen (when the sending device first saw the entry, since record entries carry no time), sent (when it was sent), device (the member’s device, for activity), received (ZestSSH Cloud’s, for a note that could not be read).
actorWho did it: uid and email.
target, approver, crate, role, until, offline_hours, sessionRecord events, when the change has them: the member it is about, a second owner who approved, the vault, the role, an end time, a vault’s offline limit, a session that ran with nobody watching.
recordRecord events: the entry’s seq, hash and prev, which tie the event to the signed record itself.
received, crate, itemActivity: when ZestSSH Cloud took the note, the vault, and the item’s kind, id and name.
messageThe event in an English sentence.
keyThe key the event is signed with, in base64.
sigThe signature, in base64.

key is the Ed25519 public key that the team’s record names, the one you copied with Copy the key. sig is that key’s signature over the UTF-8 bytes of

zestssh.export-event.v1
<canonical>

where <canonical> is the event without its sig member (with key still in it), as JSON with the keys of every object sorted, no spaces, and strings escaped the shortest way: only the quote, the backslash and the characters below U+0020. In Python:

import base64, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
def verify(event: dict, expected_key_b64: str) -> bool:
if event.get("key") != expected_key_b64:
return False
body = {k: v for k, v in event.items() if k != "sig"}
canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
message = ("zestssh.export-event.v1\n" + canonical).encode("utf-8")
try:
Ed25519PublicKey.from_public_bytes(base64.b64decode(expected_key_b64)).verify(
base64.b64decode(event["sig"]), message)
return True
except Exception:
return False

In JavaScript, JSON.stringify of an object rebuilt with sorted keys gives the same bytes. Compare key with the key you were given, and do not trust the one an event names: anyone can sign an event with a key of their own.

  • Everyone else in the team, when it is turned on or off: for example “Example Ops now sends its events on”, with who turned it on. A device that opens the team for the first time while it is on says so once.
  • Members on the Rules page, while it is on: Event export says who set it up, and that changes to the team, and what they do with its items, also go to a system the owners chose, outside ZestSSH.
  • People invited, before they join: It also sends its events on to a system of its own, outside ZestSSH.

Change the address, or send from another device

Section titled “Change the address, or send from another device”
  • Change the address: on the sending device, choose Change the address. Only that device holds the address; the record never did.
  • Send a test event: on the sending device, while it is on.
  • Send from this device: on another owner’s device. It takes over: events are signed with a new key from then on, so give the receiver the new key. The device that sent until then sends what it had left the next time ZestSSH runs on it, and then stops.
  • Copy the key: any owner, while it is on.

Any owner can choose Turn off and confirm Stop sending Example Ops’s events on?, with a recent sign-in. Turned off on the sending device, that device first sends what it had not sent yet, then This device sends that it was turned off as its last event, then forgets the key and the address.

Sending also ends by itself when activity is turned off, and when the owner who set it up is no longer an owner.

When sending ends by a change made on another device, the sending device sends the record’s entries up to the change that ended it, then forgets the key and the address. What members did between its last round (a minute at most while it runs) and that change is not sent on. When activity was turned off, ZestSSH Cloud deleted that activity too.

  • The sending device has to run. Events flow only while ZestSSH runs on it. What happens meanwhile goes when it runs again. ZestSSH Cloud keeps activity 90 days, so a device that stays off longer than that misses what is older.
  • A device that never runs again leaves what it had not sent unsent, whoever takes over. ZestSSH Cloud takes no part in sending, so no other device can tell that the sender went quiet.
  • After a takeover, the old device sends what it had left to the address it was given, including what members did until it noticed the takeover. To stop that, turn it off or change the address on that device first.
  • A lost sending device holds the signing key, the address and the header. Take over from another device with Send from this device, give the receiver the new key, and replace the token your collector accepts.
  • A device that no longer sends does not keep trying. If the receiver refuses its last events after sending ended, it stops anyway.
  • Messages are in English, whatever language the app is in.