Private status pages
A status page does not have to be open to the world. Sentro offers three modes:
| Mode | Who can see the page |
|---|---|
| Anyone | Everybody with the address. This is the default. |
| Only with a signed link | Only visitors arriving on a link signed with the page's secret. |
| Nobody | The page is switched off for everyone, including your own team. |
This page explains the middle one.
How it works
Sentro does not ask a visitor to sign in. Instead, your own system decides who may look, and proves that decision by signing a link.
- Somebody signs in to your application, wherever that happens.
- Your application builds a link to the status page and signs it with the page's secret, choosing how long the link should stay valid.
- The visitor follows the link. Sentro checks the signature and the expiry, then renders the page.
- Anyone without a valid link gets nothing — the page is indistinguishable from one that does not exist.
Nothing is stored, and Sentro never learns who the visitor is. There is no account to create, no session to manage, and no user list to keep in step.
Turning it on
On the status page's settings screen, set Who can see this page to Only with a signed link, then save. Saving generates the page's signing secret.
Use Reveal secret to read it, and give it to the system that will hand out links. Treat it exactly like a password: anyone holding it can open the page and can let anybody else in.
The settings screen also produces an example link valid for one hour, so you can confirm the page works before wiring anything up.
Withdrawing access
Generate new secret replaces the secret. Every link already handed out stops working immediately. This is how you cut off a system you no longer trust, or respond to a leaked secret.
Signing a link
A signed link is the page's normal address plus two query parameters:
https://app.sentro.dev/status/YOUR-SLUG?expires=1786000300&signature=8f2c...
| Parameter | Meaning |
|---|---|
expires | The moment the link stops working, as a Unix timestamp in seconds. A whole number. |
signature | HMAC-SHA256 of the signing string, keyed with the page's secret, written as lowercase hexadecimal. |
The signing string is the page's slug and the expiry, joined by a single colon:
YOUR-SLUG:1786000300
That is the entire recipe. Both values are inside the signature, so a signature cannot be moved to a different page and an expiry cannot be extended after the fact.
Sentro allows 60 seconds of clock difference between your system and its own, so a very short-lived link does not fail because one clock is slightly behind.
Examples
import { createHmac } from 'node:crypto';
function statusPageLink(slug, secret, validForSeconds = 3600) {
const expires = Math.floor(Date.now() / 1000) + validForSeconds;
const signature = createHmac('sha256', secret).update(`${slug}:${expires}`).digest('hex');
return `https://app.sentro.dev/status/${slug}?expires=${expires}&signature=${signature}`;
}
import hmac, hashlib, time
def status_page_link(slug: str, secret: str, valid_for_seconds: int = 3600) -> str:
expires = int(time.time()) + valid_for_seconds
signature = hmac.new(
secret.encode(), f"{slug}:{expires}".encode(), hashlib.sha256
).hexdigest()
return f"https://app.sentro.dev/status/{slug}?expires={expires}&signature={signature}"
<?php
function statusPageLink(string $slug, string $secret, int $validForSeconds = 3600): string {
$expires = time() + $validForSeconds;
$signature = hash_hmac('sha256', "$slug:$expires", $secret);
return "https://app.sentro.dev/status/$slug?expires=$expires&signature=$signature";
}
SLUG="acme-cloud"
SECRET="your-page-secret"
EXPIRES=$(( $(date +%s) + 3600 ))
SIGNATURE=$(printf '%s' "$SLUG:$EXPIRES" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
echo "https://app.sentro.dev/status/$SLUG?expires=$EXPIRES&signature=$SIGNATURE"
func statusPageLink(slug, secret string, validFor time.Duration) string {
expires := time.Now().Add(validFor).Unix()
mac := hmac.New(sha256.New, []byte(secret))
fmt.Fprintf(mac, "%s:%d", slug, expires)
return fmt.Sprintf("https://app.sentro.dev/status/%s?expires=%d&signature=%x",
slug, expires, mac.Sum(nil))
}
Choosing a lifetime
The link is the permission, so its lifetime is how long that permission lasts.
- A few minutes — for a link generated the moment somebody clicks through from your application. Tightest option; a forwarded link is useless almost immediately.
- An hour — a good default. Long enough to read the page and follow its own links, short enough that a link pasted into a chat stops working the same morning.
- A day or more — only where you are comfortable with the link being effectively shareable for that long.
Two things worth knowing:
- A signed link can be forwarded. Whoever holds it can open the page until it expires. Short lifetimes are the defence; generate a link per click rather than embedding a long-lived one in a page or an email.
- The page's own links keep the signature. Changing the date range or paging through incidents carries the signature along, so a visitor is not locked out of a page they are already reading. If the link expires while they are on it, reloading fails and they simply click through from your application again.
What a private page does differently
- Search engines are asked not to index it, and the page's title and description give nothing away.
- Email subscriptions are not offered. Accepting an address on a private page would confirm to anybody guessing addresses that the page exists.
- Refusals look like a missing page. A wrong signature, an expired link and a page that was never created are indistinguishable from the outside.
If the page will not open
| What you see | Likely cause |
|---|---|
| Page not found, with a link you just generated | The slug in your signing string does not match the slug in the address, or the secret has been regenerated since. |
| Worked a moment ago, not now | The link expired. Check you are adding your lifetime to the current time, in seconds. |
| Never works, from any system | The signature is not lowercase hexadecimal of an HMAC-SHA256, or the signing string has extra whitespace or a different separator. It is exactly slug:expires. |
| Works for you, not for a colleague | Nothing about the visitor is checked, so this is a copied-link problem — usually a truncated signature. |