Bot Credentials and Webhooks
Each bot has its own credentials, separate from the partner organization's API key.
Recommended security model
| Credential | Purpose | Storage |
|---|---|---|
Partner API key (sk_…) | Manage all bots, rotate secrets, and send hub messages | Hashed 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 HMAC | Stored because it is required for signing; treat it like a password |
| Webhook URL | Partner HTTPS endpoint for MorisBox events | Plaintext 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
- HTTPS webhooks only (localhost is allowed in development)
- HMAC-SHA256 body signatures on every outbound event
- Optional per-bot IP allowlist
- Rotate the token / secret without recreating the bot
- 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_tokenandwebhook_secretin your secrets manager. They cannot be retrieved later; they can only be regenerated through rotation.
Manage credentials
| Method | Path | Authentication | Description |
|---|---|---|---|
| GET | /bots/{code}/credentials | Partner key or bot token | Prefixes only |
| POST | /bots/{code}/token/rotate | Partner key | New bot_token (once) |
| PUT | /bots/{code}/webhook | Partner key | Set webhook_url, optional rotate_secret |
| POST | /bots/{code}/webhook/secret/rotate | Partner key | New webhook secret |
| POST | /bots/{code}/webhook/test | Partner key | Send a signed test event |
| PATCH | /bots/{code} | Partner key | Update 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:
Authorization: Bearer <bot_token>X-Seed360-Bot-Secret: <webhook_secret>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
- Create the bot → store
bot_tokenandwebhook_secret - Deploy the HTTPS receiver →
PUT …/webhook - Run
POST …/webhook/testuntilok: true - After a leak, rotate through
token/rotate/webhook/secret/rotate