Relay Runtime Quickstart
Use relay as a secure inbound event channel. Start by registering a bot with POST /api/public/bots/register, claim it as owner, then deliver runtime events through @twire/relay.
1) Register bot (public endpoint)
Send this exact JSON shape. If key names differ, registration is rejected.
curl -i "https://bot-relay.com/api/public/bots/register" \
-X POST \
-H "content-type: application/json" \
--data '{
"id": "CBHB-01",
"targetUrl": "https://runtime.example.com/relay/events",
"description": "CBHB production runtime"
}'Allowed payload fields
{
"id": "required, [A-Za-z0-9._:-]{3,128}",
"targetUrl": "required, full http:// or https:// URL",
"description": "optional, max 512 chars"
}Success response (201)
{
"botId": "CBHB-01",
"status": "unclaimed",
"claimCode": "paste-this-into-claim-flow",
"runtimeSecret": "set-this-as-TWIRE_RELAY_SECRET",
"claimExpiresAt": "2026-03-05T12:34:56.000Z"
}Save runtimeSecret immediately as TWIRE_RELAY_SECRET on the runtime host.
Self-serve bootstrap (shell + jq)
BOT_ID="CBHB-01"
TARGET_URL="https://runtime.example.com/relay/events"
DESCRIPTION="CBHB production runtime"
RELAY_URL="https://bot-relay.com"
OUT_DIR="${HOME}/.bot-relay/${BOT_ID}"
mkdir -p "${OUT_DIR}"
REGISTER_RESPONSE="$(
curl -fsS "${RELAY_URL}/api/public/bots/register" \
-X POST \
-H "content-type: application/json" \
--data "{
\"id\": \"${BOT_ID}\",
\"targetUrl\": \"${TARGET_URL}\",
\"description\": \"${DESCRIPTION}\"
}"
)"
printf "%s\n" "${REGISTER_RESPONSE}" | tee "${OUT_DIR}/register.json"
printf "%s" "${REGISTER_RESPONSE}" | jq -e '.runtimeSecret and .claimCode' >/dev/null
RUNTIME_SECRET="$(printf "%s" "${REGISTER_RESPONSE}" | jq -r '.runtimeSecret')"
CLAIM_CODE="$(printf "%s" "${REGISTER_RESPONSE}" | jq -r '.claimCode')"
cat > "${OUT_DIR}/runtime.env" <<EOF
TWIRE_RELAY_URL="${RELAY_URL}"
TWIRE_BOT_ID="${BOT_ID}"
TWIRE_RELAY_SECRET="${RUNTIME_SECRET}"
TWIRE_HIVE_MIND_LOG_PATH="/var/lib/bot-relay/hive-mind/events.jsonl"
TWIRE_DAILY_BUFFER_PATH="/var/lib/bot-relay/hive-mind/daily-buffer.log"
TWIRE_RUNTIME_STATE_FILE="/var/lib/bot-relay/runtime/state.json"
EOF
echo "claimCode=${CLAIM_CODE}"
echo "runtime env file: ${OUT_DIR}/runtime.env"Copy runtime.env to the runtime host (for example /etc/bot-relay/runtime.env) and start npm run start:runtime.
Common rejections
invalid_registration_payload: JSON did not match { id, targetUrl, description? }.
unsupported_target_protocol: targetUrl must use http or https.
bot_id_already_exists: choose a new bot ID.
registration_rate_limited: too many attempts from one IP in one minute.
2) Claim bot as owner
Sign in with passkey at /, open /dashboard, and claim using the returned claimCode.
POST /api/admin/bots/{id}/claim
{
"claimCode": "paste-from-register-response"
}3) Send inbound events
External senders post to POST /b/{id}. Relay prescreens, stores audit metadata, and queues accepted work for runtime delivery.
{
"event": "example.event",
"payload": { "ok": true },
"timestamp": "optional ISO date-time",
"meta": { "optional": "object" }
}Runtime tunnel security (target vNext)
Keep the runtime private and bot-initiated outbound. Treat tunnel payloads as untrusted until signature and replay checks pass.
Hosted runtime endpoints
POST /api/relay/runtime/pull
POST /api/relay/runtime/ack
POST /api/relay/runtime/nack
Runtime pull/ack/nack requests must include: x-twire-bot-id and x-twire-runtime-secret.
Required headers
x-twire-bot-id
x-twire-timestamp
x-twire-nonce
x-twire-signature
Signature base string
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + SHA256(BODY)
Bot runtime setup with @twire/relay
Install first:
npm install @twire/relay
Configure the runtime package with relayUrl, botId, and secret material. Register an event handler, verify delivery authenticity, then ack/nack based on processing outcome.
Default controls: 300s timestamp skew, 10m nonce TTL, 24h idempotency retention.
Smoke test command
npm run smoke:runtime:ci
Security checklist
Enforce signature verification before business logic.
Reject stale timestamps beyond 300 seconds.
Store nonce keys for 10 minutes to block replays.
Store idempotency keys for 24 hours.
Use retryable nack only for transient failures.
Owner checklist
Keep bots in ACTIVE state only after claim + target setup.
Rotate shared secrets and maintain rollout logs.
Monitor dead-letter queues and signature failures.
Dashboard tunnel badge is active when runtime heartbeat is fresh.
Webhook worker automatically backs off when runtime heartbeat is active.
Document ack/nack expectations with bot integrators.
Related paths
POST /b/{id}
GET /b/{id}
POST /api/public/bots/register
POST /api/relay/runtime/pull
POST /api/relay/runtime/ack
POST /api/relay/runtime/nack