Warmbly文档

OAuth

通过授权码流程,允许第三方应用代表 Warmbly 工作区执行操作。

API 密钥适用于你自己的脚本。如果你正在构建供其他人连接其 Warmbly 工作区的应用,应使用 OAuth:用户在同意授权页面向应用授予限定范围的访问权限,你将获得代表用户执行操作的令牌。整个过程中,你不会看到用户的密码或 API 密钥。

Warmbly 实现了带 PKCE 的 OAuth 2.1 授权码流程。机密客户端应用(在控制台注册)持有客户端密钥;公共客户端(原生、移动和 CLI 应用,包括自行注册的 MCP 客户端)使用 PKCE,无需客户端密钥。不支持隐式授权和密码授权。

注册应用

在控制台中打开设置 -> OAuth 应用,注册一个应用。需要提供:

  • 名称,以及可选的说明、徽标和网站(将在同意授权页面显示)。名称需遵循命名规则,不得包含 Warmbly,包括插入空格或使用外形相似字符的写法,以免用户将应用误认为 Warmbly 官方应用。已经使用这类名称的应用可保留现有名称,直到名称被修改;
  • 一个或多个重定向 URI(用户批准后返回的位置),必须精确匹配并使用 HTTPS,原生应用的回环 URL 除外;
  • 应用可请求的权限范围(除 API_KEYS 外的任意权限;该权限仅供用户和 API 密钥使用);
  • 可选的允许的 Webhook 域名(见下文)。

应用管理接口(/oauth/applications/* 和 /oauth/application-logo)仅供已登录成员或 API 密钥使用。使用 OAuth 访问令牌会被拒绝,返回 403 oauth_token_not_allowed,因此应用永远不能注册其他应用、轮换密钥或读取 Webhook 密钥。

你会获得客户端 ID和客户端密钥;密钥仅显示一次。请将密钥保存在服务端,用于验证令牌交换请求。

应用徽标的工作方式与工作区徽标相同:上传到应用后,由 Warmbly 保存并设置。

  • POST /oauth/applications/{id}/logo 接收多部分表单中的 file:PNG 或 JPG 格式,最大 2 MB,宽高均需介于 32 至 1,024 像素。Warmbly 会解码图片并保存一份重新生成的副本,因此只保留像素,不保留相机、位置元数据或图片末尾的额外字节。响应为更新后的应用,旧徽标会被删除。
  • DELETE /oauth/applications/{id}/logo 用于移除徽标。

与应用的其他管理操作一样,两者均需 权限范围 API_KEYS 和组织权限 manage_api_keys。徽标会向所有看到同意授权页面或应用目录的工作区展示,因此创建和更新请求体中的 logo_url 只接受本实例为你的工作区签发的地址;其他地址会被拒绝,返回 400 invalid_logo。更新时不提供 logo_url 会保留当前徽标。

应用被停用或工作区被封禁时

实例运维人员可暂停应用。被暂停的应用无法让任何人登录,其访问令牌和刷新令牌均停止工作,Webhook 投递停止,并从社区目录中移除。应用的 suspended_at 和 suspended_reason 会反映这一状态,且只有运维人员可以解除暂停。自行停用应用时,行为相同,直到你再次启用它。

运维人员还可禁止某个工作区或用户注册、发布应用。GET /oauth/applications 会在 developer_access 中报告该情况(blocked、reason),后续注册或发布请求将返回 403 developer_access_blocked。工作区已有的应用会继续运行,除非这些应用也被暂停。

公开客户端与动态注册

原生应用、CLI 工具和 MCP 客户端无法安全持有机密值。因此它们以公共客户端身份连接,不使用客户端密钥,且必须使用 PKCE。有两种方式可获取公共客户端的 client_id。

在运行时通过动态客户端注册(RFC 7591)自行注册。MCP 客户端采用此方式,让用户只需一条命令即可连接:

curl -X POST "https://api.warmbly.com/v1/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP client",
    "redirect_uris": ["http://127.0.0.1:8080/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"]
  }'
{
  "client_id": "wmcid_...",
  "client_id_issued_at": 1752624000,
  "redirect_uris": ["http://127.0.0.1:8080/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read_campaigns read_contacts ..."
}

注册端点公开开放,无需身份验证,并按来源 IP 限流。注册本身不会授予任何访问权限,仍需用户登录并在同意授权页面批准权限范围。重定向 URI 可使用 HTTPS、回环地址(http://127.0.0.1 或 localhost,任意端口),或私有协议(myapp://);授权和令牌交换时均进行精确匹配。自行注册的客户端永远不能请求 SEND_CAMPAIGNS 或 API_KEYS。

自行注册的客户端没有任何身份担保,因此同意授权页面仅显示 Warmbly 能确认的信息。client_uri 和 logo_uri 会被忽略,页面不会显示其网站或徽标。client_name 会经过清理,而非直接拒绝:括号中包含 Warmbly 名称的部分会被删除(Claude Code (warmbly) 显示为 Claude Code);若清理后仍包含 Warmbly,或名称违反命名规则,则显示为 MCP client。

公共客户端在令牌交换时不发送 client_secret,只发送 PKCE 的 code_verifier。下文流程的其余部分完全相同。如果是在连接 AI 助手,无需手动执行这些步骤;单命令配置方式见 MCP 服务器 页面。

权限范围

OAuth 权限范围与 API 密钥的权限相同,只是使用小写名称,例如 read_campaigns、write_contacts。完整列表见权限参考。令牌携带以下三个集合的交集:

  • 应用注册时声明的权限范围;
  • 授权请求所请求的权限范围(scope 为空时,表示应用的全部权限范围);
  • 根据角色映射,批准授权的成员角色所覆盖的权限范围。api_keys 永远不会授予应用。

因此,应用向 Viewer(查看者)请求 write_contacts 时,只会获得该查看者持有的读取权限范围。同意授权页面会分别列出将授予的权限,以及已请求但超出成员角色范围的权限。令牌响应中的 scope 表示实际授予的集合,可能比请求范围更小(RFC 6749 第 3.3 节)。如果成员角色不包含任何请求的权限范围,同意授权页面会拒绝操作,返回 access_denied。

权限上限始终随成员角色变化,而不是只在同意授权时检查。每个携带令牌的请求都会根据成员的当前角色进行检查:降低其角色权限后,令牌也会失去新角色未涵盖的权限;调用所需权限已不再持有时,返回 403。刷新时,如果成员已不具备任何已授予的权限范围,则返回 invalid_grant。

授权流程

1. 将用户引导至授权页面

使用标准参数将用户浏览器重定向到同意授权页面。PKCE 可选,但推荐使用:生成 code_verifier(高熵随机字符串),并发送其 S256 挑战值。

code_challenge = base64url( sha256( code_verifier ) )
https://app.warmbly.com/oauth/authorize
  ?response_type=code
  &client_id=wmcid_...
  &redirect_uri=https://yourapp.com/oauth/callback
  &scope=read_campaigns%20read_contacts
  &state=<random-csrf-token>
  &code_challenge=<challenge>
  &code_challenge_method=S256

返回时应由你验证 state,以防范 CSRF。code_challenge 和 code_challenge_method 为可选项;如果提供,则 code_challenge_method 必须为 S256。

2. 用户批准授权

Warmbly 会显示应用、将被授予访问权限的工作区、将授予的权限范围、批准后访问权限的接收位置(重定向 URI 的主机;对于回环或私有协议重定向,则为本设备上的应用),以及授权或拒绝选项。除非应用是在控制台注册而非自行注册,且被实例列为社区目录中的精选应用,否则会标记为 Unverified app(未经验证的应用)。未经验证的应用还会显示 Not affiliated with Warmbly(与 Warmbly 无关联) 提示,要求成员仅批准自己主动发起的连接。由于批准会向应用交付持续有效的凭据,如果成员在最近几分钟内未登录或重新验证,批准时会要求其确认身份。批准后,浏览器会携带一次性代码重定向回 redirect_uri:

https://yourapp.com/oauth/callback?code=wmac_...&state=<your-state>

如果用户拒绝,重定向会改为携带 ?error=access_denied&state=...。

3. 用授权码换取令牌

验证 state 一致后,向令牌端点发起 POST 请求。请求类型为 application/x-www-form-urlencoded;发送 client_secret(放在请求体中,或通过 HTTP Basic 身份验证传入),如果使用 PKCE,还需发送原始的 code_verifier。公共客户端省略 client_secret,并始终发送 code_verifier。

curl -X POST "https://api.warmbly.com/v1/oauth/token" \
  -d grant_type=authorization_code \
  -d code=wmac_... \
  -d redirect_uri=https://yourapp.com/oauth/callback \
  -d client_id=wmcid_... \
  -d client_secret=wmcs_... \
  -d code_verifier=<the-original-verifier>
{
  "access_token": "wmat_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "wmrt_...",
  "scope": "read_campaigns read_contacts"
}

4. 调用 API

访问令牌的使用方式与 API 密钥完全相同:

curl "https://api.warmbly.com/v1/campaigns" \
  -H "Authorization: Bearer wmat_..."

5. 令牌到期后刷新

访问令牌有效期为一小时。可在过期前后用刷新令牌换取新的一对令牌。刷新令牌会轮换:新令牌对一经签发,旧刷新令牌立即失效,因此务必保存最新令牌。提交已交换过的刷新令牌,例如两个工作节点并行刷新、使用旧令牌重试或重放,会撤销该授权,用户必须重新授权(RFC 9700 第 4.14.2 节)。同一授权的刷新操作必须串行执行。

curl -X POST "https://api.warmbly.com/v1/oauth/token" \
  -d grant_type=refresh_token \
  -d refresh_token=wmrt_... \
  -d client_id=wmcid_... \
  -d client_secret=wmcs_...

撤销

用户断开连接时,应撤销访问令牌或刷新令牌及其关联授权:

curl -X POST "https://api.warmbly.com/v1/oauth/revoke" \
  -d token=wmat_... \
  -d client_id=wmcid_... \
  -d client_secret=wmcs_...

用户也可以自行撤销应用授权;具有工作区凭据管理权限(manage_api_keys 或 manage_settings)的成员,可在设置 -> OAuth 应用 -> 已授权应用下撤销任何成员的授权。两种操作都会使该成员授权签发的所有令牌失效。用户被移出工作区时,其在该工作区授予的所有授权也会以相同方式失效,因此下一次刷新预计会返回 invalid_grant。

工作区授权

这些端点仅接受会话,供管理工作区凭据的人员使用。两者均需要组织权限 manage_api_keys 或 manage_settings。

  • GET /oauth/workspace-authorizations 返回 {"authorizations": [...]}:每个应用与成员组合对应一行,包含 application_id、name、logo_url、website_url、user_id、user_email、user_name、scopes、authorized_at 和 last_used_at。只列出仍有效的授权,即未撤销且刷新令牌未过期的授权。
  • DELETE /oauth/workspace-authorizations/{application_id}/members/{user_id} 撤销该成员对该应用的授权,并返回 {"revoked": true}。没有可撤销内容时也会成功,因此可以安全重试。

令牌有效期

令牌有效期说明
授权码10 分钟一次性使用,绑定 PKCE
访问令牌1 小时Bearer 令牌,携带已授予的权限范围,并受成员当前角色限制
刷新令牌90 天每次使用都会轮换;重复使用旧令牌会撤销授权

刷新令牌已过期的授权不再计为一次安装:不会接收应用 Webhook,不计入目录安装数,也不会出现在已授权应用列表中。

发现机制

端点 URL 和支持的能力会公开发布,供客户端自动配置。

授权服务器元数据(RFC 8414):

GET https://api.warmbly.com/.well-known/oauth-authorization-server

其中列出授权、令牌、撤销和注册端点、支持的权限范围、唯一的响应类型 code、authorization_code 和 refresh_token 授权类型、唯一的 PKCE 方法 S256,以及客户端身份验证方式 none(公共客户端)、client_secret_basic 和 client_secret_post。

MCP 端点的受保护资源元数据(RFC 9728):

GET https://api.warmbly.com/.well-known/oauth-protected-resource

其中标明资源及其授权服务器,因此 MCP 客户端从 /v1/mcp 收到带 WWW-Authenticate 质询的 401 响应时,可以发现应在哪里进行身份验证。请参阅 MCP 服务器 页面。

OAuth 应用的 Webhook 域名

如果应用代表用户注册 Webhook 端点,必须声明这些端点可使用的域名。创建或更新应用时,在应用对象上设置 allowed_webhook_domains(字符串数组):

{
  "name": "My App",
  "redirect_uris": ["https://yourapp.com/oauth/callback"],
  "allowed_webhook_domains": [".acme.com", "hooks.partnersite.com"]
}

应用注册的任何 Webhook URL,其主机都必须位于此列表允许的范围内。空列表表示应用完全不能注册 Webhook 端点。

匹配规则:

  • 不带前导点的条目(acme.com)仅匹配完全相同的主机。
  • 带前导点的条目(.acme.com)匹配根域名及任意子域名:acme.com、hooks.acme.com、prod.hooks.acme.com。

端点注册时会强制检查此列表,投递时也会重新检查。完整的端点验证和投递流程见Webhook 参考。

应用接收事件(Webhook)

应用无需为每个安装它的工作区保持一条 WebSocket 连接,而可以声明一份 Webhook 配置,让 Warmbly 自动将每个授权组织的事件投递到该地址。这采用 GitHub / Slack 应用的模式:在应用上配置一次,即可接收所有安装组织的事件。

应用 Webhook 配置

通过 POST /oauth/applications 或 PATCH /oauth/applications/:id 创建或更新应用时,设置 webhook_url 和 webhook_events。

字段类型说明
webhook_urlstring接收事件投递的 HTTPS URL。其主机必须位于应用的 allowed_webhook_domains 允许范围内,否则返回 400。设为空值可停止应用级 Webhook 投递。
webhook_eventsstring[]应用订阅的事件名称。数组为空或省略时,订阅组织所授予权限范围允许的全部非全量流式事件。

签名密钥由服务器生成,整个应用共用一个密钥,用于签署来自所有安装组织的投递。与各端点自身的副本一样,它使用实例凭据密钥加密存储。使用下方专用端点查看或轮换密钥。

{
  "name": "My App",
  "redirect_uris": ["https://yourapp.com/oauth/callback"],
  "allowed_webhook_domains": [".yourapp.com"],
  "webhook_url": "https://hooks.yourapp.com/warmbly",
  "webhook_events": ["campaign.reply_received", "meeting.booked", "contact.created"]
}

根据权限范围自动投递

组织授权应用后,Warmbly 会自动为该组织创建受管理的 Webhook 端点,并开始投递订阅的事件。组织撤销应用授权后,该端点会被移除。

可投递事件取决于应用在 webhook_events 中声明的事件,与组织所授予权限范围允许事件的交集。只有应用持有相应权限范围且组织也已授予该权限时,才会投递该事件类别。权限范围到事件的映射如下:

事件所需权限范围
inbox.*READ_UNIBOX
campaign.*, deliverability.*, meeting.*READ_CAMPAIGNS
email_account.*, warmup.*READ_EMAILS
contact.*, bulk_operation.*READ_CONTACTS
crm.*READ_CRM
automation.*INTEGRATIONS
team.*, role.*, settings.*, subscription.*READ_AUDIT_LOGS
custom.eventREALTIME_SUBSCRIBE

每次投递的载荷都包含 organization_id,便于识别事件来自哪个安装组织。投递会签名,并按普通 Webhook 相同的退避计划重试,可使用同样的 X-Warmbly-Signature 机制验证。验证算法见Webhook:验证签名。

应用 Webhook 管理端点

四个端点均需 权限范围 API_KEYS 和组织权限 manage_api_keys。

查看 Webhook 密钥

GET /oauth/applications/{id}/webhook-secret

返回当前签名密钥。该值带 whsec_ 前缀,用于验证此应用在所有安装组织中的投递。

{ "webhook_secret": "whsec_9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0" }

轮换 Webhook 密钥

POST /oauth/applications/{id}/webhook-secret/rotate

签发新的签名密钥,并仅返回一次。在使用旧密钥签名的在途投递完成前,应更新验证逻辑。

{ "webhook_secret": "whsec_1a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081" }

列出各组织的端点

GET /oauth/applications/{id}/webhook-endpoints

返回 Warmbly 为此应用实际创建的各组织受管理端点,以及当前健康状态。

{
  "endpoints": [
    {
      "id": "a3f1c2e4-5b6d-7e8f-9a0b-1c2d3e4f5a6b",
      "organization_id": "11111111-2222-3333-4444-555555555555",
      "url": "https://hooks.yourapp.com/warmbly",
      "enabled": true,
      "verified_at": "2026-06-14T10:00:00Z",
      "consecutive_failures": 0,
      "last_success_at": "2026-06-14T10:05:00Z",
      "last_failure_at": null
    }
  ]
}

跨组织投递日志

GET /oauth/applications/{id}/webhook-deliveries

返回所有安装组织的投递日志,按最新优先排序。每次尝试均包含响应码、错误、载荷和重试状态。

参数位置类型说明
statusquerystring按状态筛选:pending、in_flight、delivered、failed 或 abandoned。
event_typequerystring按事件类型筛选,例如 campaign.reply_received。
limitqueryinteger最大行数。范围为 1 至 200,默认 50。超出范围会返回 400。
cursorquerystring来自 pagination.next_cursor 的不透明分页游标。

响应采用标准的 data + pagination 封装和不透明游标。每行都包含 organization_id,便于查看各次投递所属的安装组织。

Webhook 与实时网关的区别

实时网关和应用级 Webhook 使用同一套事件名称。请根据集成需求选择:

实时网关应用级 Webhook
传输方式WebSocket(持久连接)HTTP POST(无状态投递)
适用场景实时界面、在线状态、低延迟仪表板持久化处理、数据管道、无服务器接收端
连接模式每个安装组织一条连接无需持久连接;Warmbly 向你的 URL 推送
投递保证断开连接时可能丢失事件,支持恢复和重放指数退避重试,可查看日志
权限范围检查按连接的订阅意图检查按应用权限范围检查,并对每个组织的授权强制执行

两种方式均可采用,取决于所构建的功能。类似仪表板的集成适合使用 WebSocket 的低延迟和在线状态功能。数据管道或无服务器集成则适合 Webhook 的持久化 HTTP 投递:每次投递都可单独检查,也无需为每个安装组织保持连接。

发布到社区目录

应用正常运行后,可发布到社区目录。在设置 -> OAuth 应用中使用应用的发布操作,或调用以下端点。

已发布的目录条目具有 status:

  • published:仅通过链接访问。可在 /app/integrations/apps/{slug} 打开,并提示该应用通过链接分享;在发布者以外的至少 25 个工作区安装之前,不会出现在目录、搜索或推荐中,且这些工作区各自都必须已创建至少两周。
  • featured:由实例团队选为精选。无论安装数多少,都会出现在目录中并带有精选标记。
  • hidden:团队已将其下架。任何位置均不可访问,status_note 会说明原因。

对目录条目,或应用名称、徽标、网站、权限范围的任何修改,都会使 featured 条目恢复为 published。没有实际变更的保存会保留状态;hidden 条目在编辑后仍保持隐藏。

目录中的安装操作会打开 install_url。从该地址使用你自己的 state 启动授权码流程,让用户像从你的网站发起连接一样,进入 Warmbly 同意授权页面。

三个端点均需 权限范围 API_KEYS 和组织权限 manage_api_keys。

获取目录条目

GET /oauth/applications/{id}/listing

返回 {"listing": {...}};应用尚未发布时返回 {"listing": null}。

发布或编辑目录条目

PUT /oauth/applications/{id}/listing

替换整个目录条目,因此可以安全地重复请求。

字段类型说明
slugstring链接标识。由 3 至 48 个小写字母、数字或单个连字符组成,首尾必须为字母或数字。在实例内唯一;内置集成的名称为保留名称。
taglinestring单行,最多 120 个字符。
descriptionstring可选,最多 2,000 个字符。保留换行;标记语法按普通文本显示。
categorystring取值为 crm、automation、notifications、meetings、data、verification、ai、other 之一。
install_urlstring点击安装后前往的地址。必须使用 https。
support_urlstring可选的 https 链接。
privacy_urlstring可选的 https 链接。
{
  "listing": {
    "application_id": "5a8f0c1e-2b3d-4e5f-8a9b-0c1d2e3f4a5b",
    "organization_id": "11111111-2222-3333-4444-555555555555",
    "slug": "acme-sync",
    "tagline": "Push positive replies into Acme as deals",
    "description": "",
    "category": "crm",
    "install_url": "https://acme.com/warmbly/install",
    "support_url": "https://acme.com/support",
    "privacy_url": "",
    "status": "published",
    "submitted_at": "2026-10-03T09:00:00Z",
    "created_at": "2026-10-03T09:00:00Z",
    "updated_at": "2026-10-03T09:00:00Z"
  }
}

可能的拒绝响应包括 400 invalid_listing(message 指明字段)、应用已停用或动态注册时的 400 app_not_listable,以及 409 listing_slug_taken。请参阅错误码。

取消发布

DELETE /oauth/applications/{id}/listing

移除目录条目及其链接。已安装应用的工作区保留其授权,直到自行撤销。不存在目录条目时也会成功,因此可以安全重试。

浏览目录

GET /integrations/community 返回目录中列出的应用:精选应用优先,其后是已在发布者以外至少 25 个工作区安装、且这些工作区各自已创建至少两周的应用,按安装数从多到少排序。响应使用 data 加 pagination(limit 范围 1 至 200,默认 100)。GET /integrations/community/{slug} 返回一个已发布或精选条目,无论它是否出现在目录中。两者均需 权限范围 INTEGRATIONS;每个条目都会报告 installs(持有有效授权的工作区数)和 installed(调用者工作区是否持有有效授权)。请参阅集成。

安全说明

  • 令牌的操作永远不会超出批准授权成员的当前角色权限,且永远不能管理 API 密钥或 OAuth 应用。
  • 重定向 URI 采用精确匹配,因此应注册所有使用的回调地址。
  • 始终发送并验证 state。
  • 公共客户端必须使用 PKCE,机密客户端应用也建议使用;启用时,仅接受 S256。
  • 将客户端密钥保存在服务端。原生、移动和 CLI 应用应注册为公共客户端,使用 PKCE,不应嵌入密钥。
  • 动态注册公开开放,并按 IP 限流;自行注册的客户端属于公共客户端,不能请求 SEND_CAMPAIGNS 或 API_KEYS,并且只有用户在同意授权页面批准权限范围后才会获得访问权限。
  • 将 allowed_webhook_domains 限制为应用所需的最小域名集合。如果用户诱导应用注册非预期 URL,过宽的列表会扩大 SSRF 风险范围。
  • 应用 Webhook 密钥由所有安装组织共享。如果密钥泄露,应轮换并及时更新接收端:已经签名的在途投递在完成前仍需使用旧密钥验证。

本页目录