Bot RelaySecure ingress for bot runtimes

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