Bot Credentials and Webhooks

Each bot has its own credentials, separate from the partner organization's API key.

Recommended security model

CredentialPurposeStorage
Partner API key (sk_…)Manage all bots, rotate secrets, and send hub messagesHashed at rest
Bot token (bot_….…)Bot-scoped API calls (inbound webhook and future bot APIs)Hashed at rest; displayed once
Webhook secret (whsec_…)Sign / verify webhook bodies with HMACStored because it is required for signing; treat it like a password
Webhook URLPartner HTTPS endpoint for MorisBox eventsPlaintext URL

Why not OAuth client_id and client_secret?

OAuth client credentials are a better fit for access delegated by a user. Bots are machine-to-machine: one high-entropy bot token plus HMAC signatures on webhooks is simpler and more robust for this use case.

Best practices

  1. HTTPS webhooks only (localhost is allowed in development)
  2. HMAC-SHA256 body signatures on every outbound event
  3. Optional per-bot IP allowlist
  4. Rotate the token / secret without recreating the bot
  5. Never log full tokens

Create a bot (secrets returned once)

POST /api/v1/bots

{
  "code": "portail",
  "name": "SEED Portail",
  "runtime_mode": "local",
  "webhook_url": "https://partner.example/hooks/morisbox/portail"
}

201 response

{
  "bot": {
    "code": "portail",
    "credentials": {
      "token_prefix": "bot_a1b2c3",
      "token_configured": true,
      "webhook_url": "https://partner.example/hooks/morisbox/portail",
      "webhook_secret_prefix": "whsec_ab12",
      "inbound_webhook_path": "/api/v1/bots/portail/webhook"
    }
  },
  "bot_token": "bot_a1b2c3d4e5f6.xxxxxxxx",
  "webhook_secret": "whsec_xxxxxxxx",
  "warning": "Store bot_token and webhook_secret now…"
}

Store bot_token and webhook_secret in your secrets manager. They cannot be retrieved later; they can only be regenerated through rotation.


Manage credentials

MethodPathAuthenticationDescription
GET/bots/{code}/credentialsPartner key or bot tokenPrefixes only
POST/bots/{code}/token/rotatePartner keyNew bot_token (once)
PUT/bots/{code}/webhookPartner keySet webhook_url, optional rotate_secret
POST/bots/{code}/webhook/secret/rotatePartner keyNew webhook secret
POST/bots/{code}/webhook/testPartner keySend a signed test event
PATCH/bots/{code}Partner keyUpdate name, channels, allowed_ips, and more

Configure the webhook

PUT /api/v1/bots/portail/webhook
Authorization: Bearer sk_…
{
  "webhook_url": "https://partner.example/hooks/morisbox/portail",
  "rotate_secret": true,
  "allowed_ips": "203.0.113.10"
}

Outbound webhooks (MorisBox → you)

When MorisBox posts to your webhook_url:

POST https://partner.example/hooks/morisbox/portail
Content-Type: application/json
X-Seed360-Event: bot.webhook.test
X-Seed360-Bot: portail
X-Seed360-Signature: sha256=<hex>
X-Seed360-Bot-Secret: <webhook_secret>

Body

{
  "event": "bot.webhook.test",
  "bot": {
    "ref": "…",
    "code": "portail",
    "partner_code": "seed"
  },
  "payload": {}
}

Verify the signature (Python)

import hmac, hashlib

def verify(raw_body: bytes, header_sig: str, secret: str) -> bool:
    sig = header_sig.removeprefix("sha256=")
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Prefer verifying the HMAC over relying only on the secret header.


Inbound webhooks (you → MorisBox)

POST /api/v1/bots/{code}/webhook

Authenticate with one of the following:

  1. Authorization: Bearer <bot_token>
  2. X-Seed360-Bot-Secret: <webhook_secret>
  3. X-Seed360-Signature: sha256=<hmac of body with webhook_secret>
{
  "event": "custom.event",
  "data": { "ok": true }
}

Use the bot token

GET /api/v1/bots/portail
Authorization: Bearer bot_a1b2c3d4e5f6.xxxx

Bot tokens are scoped to that bot. Partner API keys manage the whole organization.


Operational checklist

  1. Create the bot → store bot_token and webhook_secret
  2. Deploy the HTTPS receiver → PUT …/webhook
  3. Run POST …/webhook/test until ok: true
  4. After a leak, rotate through token/rotate / webhook/secret/rotate