Device heartbeat (client certificate, signed body)
POST /d/v1/heartbeat
https://devices.tanba-ooh.kz/d/v1/heartbeat
Анықтамалықтың техникалық мәтіні ағылшын тілінде жазылған.
The player reports its state and receives commands. The body is signed with the device key (header X-Tanba-Signature, ES256, r||s, base64url) and carries a rising seq and the nonce given in the last answer. With wait (up to 25 seconds) the platform holds the answer until a command is ready, so a cut-off or a restore reaches a connected player within seconds. A player that makes presence tokens for its QR code sends qr_key: false until it holds its key, true after; while it is false the answer carries qr_secret and returns at once.
A clock difference above five minutes is recorded as a clock skew. A change of edid_sha256 is recorded as a changed screen. A display size (screen_width_px and screen_height_px) that differs from the size on file for the structure in every heartbeat for 24 hours is recorded as a screen size mismatch; a heartbeat without a size changes nothing, and a change of the network prefix of the caller (/24 for IPv4, /48 for IPv6) as a changed network. A silence longer than the grace of the class (10 minutes for class A and B, 60 for C) is stored as an offline interval when the player is heard again.
Аутентификация
Құрылғының клиенттік сертификаты (өзара TLS) және құрылғы кілтімен жасалған дене қолтаңбасы.
Параметрлер
| Атауы | Орны | Түрі | Міндетті | Сипаттама | Мысал |
|---|---|---|---|---|---|
X-Tanba-Signature | тақырып | string | иә | ES256 signature of the exact bytes of the body by the device key, raw r||s (64 bytes), base64url without padding. | q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw |
wait | сұрау | integer | жоқ | Seconds to hold the answer until a command is ready; 0 to 25. | 25 |
Сұраудың денесі
application/json
| Атауы | Түрі | Міндетті | Сипаттама | Мысал |
|---|---|---|---|---|
seq | integer | иә | Number of the heartbeat; it must be above the last one taken. | 1840 |
state | string | иә | State of the player. normal, compliance, cutoff | |
nonce | string | иә | The nonce or next_nonce of the last answer. | "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60" |
firmware | string | Firmware version. | "4.2.1" | |
spec_version | string | Version of the conformance specification. | "1.1" | |
log_head_hash | string | Hex SHA-256 of the newest line of the hash-chained local log. | "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123" | |
ts | string (date-time) | The clock of the player. | "2026-10-14T09:05:00Z" | |
current_approval | string | Number of the approval on screen now. | "TNB-2026-000123" | |
revocation_list_version | integer | Version of the revocation list in use. | 42 | |
edid_sha256 | string | SHA-256 of the EDID block of the display, in hex. | "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391" | |
screen_width_px | integer | Width in pixels of the display as the player sees it. Send with screen_height_px or not at all; a pair that differs from the size on file for the structure for 24 hours is recorded as a screen size mismatch. | 1920 | |
screen_height_px | integer | Height in pixels of the display as the player sees it; see screen_width_px. | 1080 | |
qr_key | boolean | False until the player holds its presence key, true after. | true |
Мысал
{
"seq": 1840,
"state": "normal",
"nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
"firmware": "4.2.1",
"spec_version": "1.1",
"ts": "2026-10-14T09:05:00Z",
"current_approval": "TNB-2026-000123",
"revocation_list_version": 42,
"qr_key": true
}
Жауаптар
200 Accepted.
application/json
| Атауы | Түрі | Міндетті | Сипаттама | Мысал |
|---|---|---|---|---|
server_time | string (date-time) | иә | The clock of the platform. | "2026-10-14T09:05:00Z" |
next_nonce | string | иә | The nonce for the next heartbeat. | "c4e7a19b0d2f63858a1e9c7b3d05f246" |
commands | array of string | иә | Signed command envelopes addressed to this player, base64 of the DER form; at most the oldest few not yet acknowledged. | |
revocation_list | object | null | The newest revocation list: its version and address. | ||
revocation_list.version | integer | Version of the list. | 42 | |
revocation_list.url | string | Address of the list. | "https://devices.tanba-ooh.kz/d/v1/revocations/42" | |
telemetry_cut_s | integer | иә | Seconds after which the player cuts a telemetry batch; it also cuts at 1000 events. | 300 |
qr_secret | string | 64 hex characters, present only while the player reports qr_key false. | "7b1d4e90a2c65f38e0b7a91d3c5f8264b0e9d1a7c3f5826e4b0d9a1c7e3f5824" |
Мысал
{
"server_time": "2026-10-14T09:05:00Z",
"next_nonce": "c4e7a19b0d2f63858a1e9c7b3d05f246",
"commands": [
"MIIC0QYJKoZIhvcNAQcCoIICwjCCAr4CAQExDzANBglghkgBZQMEAgEFADA"
],
"revocation_list": {
"version": 42,
"url": "https://devices.tanba-ooh.kz/d/v1/revocations/42"
},
"telemetry_cut_s": 300,
"qr_secret": "7b1d4e90a2c65f38e0b7a91d3c5f8264b0e9d1a7c3f5826e4b0d9a1c7e3f5824"
}
Қателер
Қателер problem құжаты (application/problem+json) түрінде, тұрақты code өрісімен келеді.
| Мәртебесі | Мағынасы |
|---|---|
| 401 | The signature does not verify (bad_signature), or the device is unknown, revoked or expired (device_unauthorized). |
| 409 | The seq is not above the last one (replay) or the nonce is not the one issued (nonce_mismatch; the problem carries next_nonce). |
| 422 | Field errors (validation_failed). |
| default | An error as a problem document. |
Код мысалдары
curl
curl -X POST 'https://devices.tanba-ooh.kz/d/v1/heartbeat' \
-H 'X-Tanba-Signature: q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw' \
-H 'Content-Type: application/json' \
-d '{
"seq": 1840,
"state": "normal",
"nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
"firmware": "4.2.1",
"spec_version": "1.1",
"ts": "2026-10-14T09:05:00Z",
"current_approval": "TNB-2026-000123",
"revocation_list_version": 42,
"qr_key": true
}'
Python
import requests
response = requests.post(
"https://devices.tanba-ooh.kz/d/v1/heartbeat",
headers={
"X-Tanba-Signature": "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw",
"Content-Type": "application/json",
},
json={
"seq": 1840,
"state": "normal",
"nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
"firmware": "4.2.1",
"spec_version": "1.1",
"ts": "2026-10-14T09:05:00Z",
"current_approval": "TNB-2026-000123",
"revocation_list_version": 42,
"qr_key": True,
},
)
print(response.status_code, response.text)
Go
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"seq": 1840,
"state": "normal",
"nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
"firmware": "4.2.1",
"spec_version": "1.1",
"ts": "2026-10-14T09:05:00Z",
"current_approval": "TNB-2026-000123",
"revocation_list_version": 42,
"qr_key": true
}`)
req, err := http.NewRequest("POST", "https://devices.tanba-ooh.kz/d/v1/heartbeat", body)
if err != nil {
panic(err)
}
req.Header.Set("X-Tanba-Signature", "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Status, string(data))
}