Skip to content

Set up check-ins

View .md

A check-in is a tap on a security key while someone writes. It shows that a person with the key was there when asked, which is the one signal on a seal that neither an agent nor your own server can produce. When check-ins vouch for every sitting anyone wrote in, the seal’s presence ring turns electric blue, shows a shield, and reads Verified Presence.

By the end of this guide, your users can add a key and choose how often to check in, from your own settings page.

Check-ins are optional and belong to the person, not the document: once someone turns them on, they apply wherever that person writes in your app. A message needs none; a take-home exam might ask for them.

Security keys are tied to the website that registered them. For a key to vouch for writing recorded by Proof, it has to be registered on Proof’s own domain, not yours. So adding keys and choosing an interval happen in Proof’s presence settings, served from embed.proof.exalto.ai: framed in your own settings page, or in a small window your page opens. Everything else about check-ins happens inside the editor.

  1. Add a presence session route. It’s like the editor’s session route, with purpose: "presence" instead of a document.

    server.ts
    app.post("/proof/presence-session", requireSignIn, async (req, res) => {
    // proofUserId: the helper from Embed the editor.
    const session = await proof.sessions.create({ user: await proofUserId(req.user), purpose: "presence" });
    res.json({ token: session.token });
    });
  2. Show them in your settings page. Frame them where your settings are, with your platform’s slug and the key’s mode:

    settings.ts
    import { mountPresenceSettings } from "@exalto/proof-embed";
    mountPresenceSettings(document.querySelector("#presence")!, {
    getToken: async () => (await (await fetch("/proof/presence-session", { method: "POST" })).json()).token,
    platform: "acme",
    mode: "live",
    }, ({ keys, everyMinutes }) => {
    presenceLabel.textContent = everyMinutes ? `Every ${everyMinutes} minutes` : "Off";
    });

    Or open them in a window of their own, which browsers that won’t create a key in a frame need:

    settings.ts
    import { openPresenceSettings } from "@exalto/proof-embed";
    document.querySelector("#presence-settings").addEventListener("click", () => {
    openPresenceSettings({
    getToken: async () => {
    const response = await fetch("/proof/presence-session", { method: "POST" });
    return (await response.json()).token;
    },
    });
    });

    Call it from a click: browsers only let a page open a window in answer to one.

  3. Let people choose. People add, order and remove keys, try a check-in (recorded nowhere), and pick how often to check in while writing: about every 1, 5, 10 or 30 minutes, or never. Framed, your callback hears each change; in the window, openPresenceSettings resolves with their choice once they close it.

    const { everyMinutes, keys } = await openPresenceSettings({ getToken });
    presenceLabel.textContent = everyMinutes ? `Every ${everyMinutes} minutes` : "Off";

To show the current setting without opening the window, read it on your server:

const { every_minutes } = await proof.users.retrieveCheckins("usr_5Kq2Lm8R");

You can also set it, for example when someone turns on check-ins from a toggle of your own:

await proof.users.updateCheckins("usr_5Kq2Lm8R", { every_minutes: 10 });

A person needs at least one key before you turn check-ins on this way: without one, updateCheckins answers 409 no_security_keys. When the person asks for presence settings in the editor, from a check-in or the breakdown, it sends a presence-settings event: open them for them.

Proof sorts each key when it’s added:

  • Hardware keys, such as a YubiKey or Windows Hello on a TPM, prove the model they are. These count fully.
  • Built-in authenticators that can’t prove their model, such as Touch ID through iCloud Keychain or Google Password Manager, are accepted as they are.
  • Password managers and unknown software are refused, because the key could be anywhere.

The kind is kept with each check-in, so a reader of the seal’s breakdown can see which keys vouched.

Nothing changes until a check-in is due. Proof asks on someone’s first edit after half an hour away from writing, then at a random time between half and one and a half times their interval. A small card appears at the foot of the editor asking for a tap. Reading, pointing and clicking never ask; writing, and accepting or rejecting suggestions, do.

A tap within a minute passes. No tap is recorded as missed. Nothing stops writing either way: a miss only means the writing that follows it isn’t verified, until the next check-in passes.

The seal’s inner ring is presence: the share of the session with a person there, writing or reading as they write. The part vouched for by check-ins is drawn in electric blue.

  • When every sitting anyone wrote in was verified, the ring is blue all round, the shield shows, and the seal line ends (verified).
  • When only some were, the line says how much, such as (73% verified).
  • Someone who doesn’t check in only goes unverified. Nothing counts against them.

The seal object carries the same numbers, so your app can show them:

const seal = await proof.seals.retrieve("AH271NSHS");
seal.presence; // { share: 97, verified_share: 97, verified: true }

See Presence and check-ins for exactly what counts as verified.

  • Seal and verify: what readers see on a sealed document.
  • Trust model: why check-ins are the signal your own server can’t produce.
  • Embed API for every option of mountPresenceSettings and openPresenceSettings.