Skip to content
On this page and tools

Hosted sites

Site host API

How a WordPress site, or your own connector, reports to Bynefit with a signed key: a connection test, visit counts and a daily health report. It is what the Bynefit Connect plugin uses, written down so you can build the same thing.

Available
Server to server only. These calls are signed with a secret key and must come from your site's server, never from a page. There is no browser access (no cross-origin headers). For calls a page makes, see the Hosted sites API.

Get a site key

An admin of the Space opens the site in the dashboard, then Settings and WordPress (the older address /dash/sites/host-keys redirects there), and chooses Generate key for that site. The key is shown once, so paste it straight into the plugin or your connector's settings. The same screen lists each key's label, first characters, last use and whether it is active, and revokes one.

Sign a request

Three headers on every call:

HeaderValue
AuthorizationBearer bynli_sh_...
X-Bynefit-TimestampThe time now in Unix seconds, digits only. It must be within 5 minutes of Bynefit's clock.
X-Bynefit-Signaturesha256= and the lowercase hex HMAC-SHA256 of the timestamp, a newline, and the exact body, keyed with the site key itself.

X-Bynefit-Timestamp and X-Bynefit-Signature are the names to send. The older X-Bynli-Timestamp and X-Bynli-Signature are still accepted, so an installed WordPress plugin keeps working. The header name is not part of what is signed. Sending a header under both names with different values is refused with a 400.

For a GET the body is empty, so the signed text is the timestamp and a newline. The query string is not signed. The key is used both to identify the site and as the signing key, so it must never leave your server.

KEY="bynli_sh_YOUR_SITE_KEY"
TS=$(date +%s)
BODY='{"kind":"heartbeat"}'
SIG="sha256=$(printf '%s\n%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.* //')"

curl -i -X POST "https://bynefit.com/api/site-host/report" \
  -H "Authorization: Bearer $KEY" \
  -H "X-Bynefit-Timestamp: $TS" \
  -H "X-Bynefit-Signature: $SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Report: connection test, visits and daily health

AvailablePOST
Request
Method and pathPOST /api/site-host/report
NeedsA site key with a signature. The body is JSON of up to 32 KB.

The body is an object with a kind. Three kinds are accepted.

heartbeat

Checks that the key and signature work. Nothing is stored.

200JSON
{ "ok": true, "site_id": 118, "slug": "riverside-aid", "kind": "heartbeat", "recorded": false }

visits

Tells Bynefit that your site got visitors, so the owners can be told. Send a count, not visitors: no IP addresses and no visitor details are accepted.

BodyJSON
{ "kind": "visits", "count": 7 }

count is clamped to 0 to 1000. The answer is {"ok": true, "site_id": 118, "kind": "visits", "recorded": true, "alerted": false}. Bynefit groups visits into quiet and roll-up windows and notifies each admin of the Space the way they chose (instant, digest or off), when the site has visit alerts on.

daily

One health report a day. Sending it again for the same date replaces that day.

BodyJSON
{
  "kind": "daily",
  "usage_date": "2026-10-10",
  "storage_bytes": 52428800,
  "bandwidth_bytes": 0,
  "request_count": 0,
  "meta": { "wp_version": "6.6", "php_version": "8.2", "plugin_version": "0.23.2" },
  "insights": {
    "updates": { "core": 0, "plugins": 2, "themes": 0 },
    "posts": 48,
    "pages": 12,
    "comments_pending": 3,
    "users_total": 5,
    "admins": 1,
    "https": true,
    "debug": false
  }
}

The first accepted report also attaches an unowned connected site to the person who made the key, which is what makes it show up in the Bynefit Sites app.

Errors

StatusBodyMeaning
401{"error":"unauthorized"}No bearer key, a malformed key, an unknown one, or a revoked one. These look the same on purpose.
400{"error":"bad_timestamp"}The timestamp is not digits only.
400{"error":"bad_signature_format"}The signature does not start with sha256=.
400{"error":"conflicting_headers"}A signing header was sent under both its X-Bynefit- and X-Bynli- names with different values.
401{"error":"signature_invalid"}The signature does not match, or the time is more than 5 minutes off.
400{"error":"body_too_large"}, invalid_json, bad_kind, bad_usage_dateThe body is over 32 KB, is not JSON, has an unknown kind, or the daily date is not YYYY-MM-DD.
405{"error":"method_not_allowed"}Wrong method.
429{"error":"rate_limited"}Too many calls on this key. Wait the seconds in the Retry-After header. The body has no wait time.
500{"error":"server_error"}Retry later.

If the signature check fails, check three things first: you signed the exact bytes you sent, you used a newline and not a dot between the time and the body, and your server clock is right.

Read the forms list

Request
Method and pathGET /api/site-host/forms
NeedsA site key with a signature and an empty body
200JSON
{ "ok": true, "team_id": 42, "forms": [ { "id": "frm_AbC123xYz", "title": "Contact us", "slug": "contact-us" } ] }

All of the Space's active forms, not only the ones for this site. At most 200.

Support tickets

A connected site can show and send Bynefit support tickets for its Space. Tickets belong to the Space, so every site key in the Space can read every ticket of that Space. A person named in wp_user_email or wp_user_name is whatever your site says; Bynefit does not verify them. A captured request can be replayed for up to five minutes, so keep these calls on your server over HTTPS.

CallWhat it does
GET /api/site-host/tickets?status=openUp to 50 tickets: open (default), in_progress, resolved or all. Returns each ticket's ref, subject, category, priority, status, reply count and a short preview, plus counts per status.
GET /api/site-host/tickets/{ref}One ticket with its replies. 404 not_found for a ref that is not in your Space.
POST /api/site-host/ticketsOpens a ticket. subject 3 to 200 characters, body up to 5000, category one of technical, billing, general, account. Answers 200 with {"ok":true,"ticket_id":...,"ticket_ref":"...","priority":"...","created_at":"..."}. Errors: 400 subject_too_short, subject_too_long, empty_subject, empty_body, invalid_category or invalid_json; 413 body_too_large.
POST /api/site-host/tickets/{ref}/replyAdds a reply (body up to 5000 characters) and re-opens the ticket.
POST /api/site-host/tickets/{ref}/resolveMarks it resolved. An optional note is posted as a reply first.

Plugin version and download

These two need no key.

Request
Method and pathGET /api/site-host/version
NeedsNothing

Returns the current Bynefit Connect release: name, slug, version, download_url, download_sha256, requires, tested, requires_php, last_updated, homepage, description and changelog (HTML). ?changelog=full returns the whole history instead of the five newest versions. Check the zip against download_sha256 before installing it. Cached for 10 minutes.

Request
Method and pathGET /api/site-host/download
NeedsNothing

Redirects (302) to the current zip. Both are limited per address; a 429 asks you to wait.

What is not part of this API