跳到主要内容

确认安装

确认应用安装,验证签名并返回 Bot Token。

接口说明​

属性值
请求方式POST
请求路径/im-user/api/v1/oauth/install/confirm
鉴权方式签名验证
重要

此接口由第三方应用后端调用,用于确认安装并获取 Bot Token。请确保在服务端调用,不要在前端暴露 clientSecret。

请求头​

Content-Type: application/json

请求参数​

参数类型必填说明
clientIdstring是应用 Client ID(UUID 格式)
workspaceIdnumber是IM 工作空间 ID
externalTenantIdstring否第三方应用租户 ID(用于建立绑定关系)
installerUserIdnumber否安装者 IM 用户 ID(用于自动绑定)
statestring否状态参数(需与发起安装时的 state 一致)
timestampnumber是签名时间戳(秒级,10位数字)
signstring是签名值(64位小写十六进制字符串)

请求示例​

curl -X POST 'https://api.im.example.com/im-user/api/v1/oauth/install/confirm' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "550e8400-e29b-41d4-a716-446655440000",
"workspaceId": 1001,
"externalTenantId": "1",
"installerUserId": 456,
"timestamp": 1736380800,
"sign": "3f8c2a1b4d5e6f7890abcdef1234567890abcdef1234567890abcdef12345678"
}'

响应参数​

参数类型说明
botTokenstringBot Token(用于调用 IM 开放 API)
tokenTypestringToken 类型,固定为 "bot"
workspaceIdnumber工作空间 ID
workspaceNamestring工作空间名称
botUserIdnumberBot 用户 ID
botNamestringBot 名称(与应用名称相同)
installationIdnumber安装记录 ID
expiresInnumberToken 有效期(秒),默认 31536000(1年)
scopestring权限范围

响应示例​

成功响应​

{
"code": 200,
"data": {
"botToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "bot",
"botUserId": 456,
"botName": "OA办公系统",
"workspaceId": 1001,
"workspaceName": "测试工作空间",
"installationId": 789,
"expiresIn": 31536000,
"scope": "bot:message:send,bot:user:read"
}
}

失败响应​

{
"code": 400,
"msg": "签名验证失败"
}

签名验证​

IM 系统会对请求进行 3 层验证:

1. 验证 Client ID​

检查 clientId 是否存在且有效。

2. 验证时间戳​

检查 timestamp 是否在有效期内:

  • 有效期:10 分钟
  • 允许时钟偏差:1 分钟

3. 验证签名​

重新计算签名并对比:

calculatedSign = SHA256(clientId + timestamp + clientSecret)

签名必须完全匹配才能通过验证。

错误码​

错误码说明解决方案
400clientId不能为空确保传递了 clientId 参数
400workspaceId不能为空确保传递了 workspaceId 参数
400timestamp不能为空确保传递了 timestamp 参数
400sign不能为空确保传递了 sign 参数
400无效的 Client ID检查 clientId 是否正确
400签名已过期签名有效期为 10 分钟,请重新发起安装
400签名验证失败检查签名计算是否正确,确保 clientSecret 正确

Java 调用示例​

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import java.util.HashMap;
import java.util.Map;

public class ImApiClient {

private final String apiBaseUrl;
private final String clientId;
private final String clientSecret;

public ImApiClient(String apiBaseUrl, String clientId, String clientSecret) {
this.apiBaseUrl = apiBaseUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
}

/**
* 确认应用安装
*
* @param workspaceId IM 工作空间 ID
* @param externalTenantId 第三方应用租户 ID
* @param installerUserId 安装者 IM 用户 ID
* @param timestamp 签名时间戳(从安装回调中获取)
* @param sign 签名值(从安装回调中获取)
* @return 安装确认响应
*/
public InstallConfirmResponse confirmInstall(
Long workspaceId,
String externalTenantId,
Long installerUserId,
Long timestamp,
String sign) {

Map<String, Object> body = new HashMap<>();
body.put("clientId", clientId);
body.put("workspaceId", workspaceId);
body.put("externalTenantId", externalTenantId);
body.put("installerUserId", installerUserId);
body.put("timestamp", timestamp);
body.put("sign", sign);

String response = HttpRequest.post(apiBaseUrl + "/im-user/api/v1/oauth/install/confirm")
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(body))
.execute()
.body();

JSONObject json = JSONUtil.parseObj(response);
if (json.getInt("code") != 200) {
throw new RuntimeException("确认安装失败: " + json.getStr("msg"));
}

return json.getBean("data", InstallConfirmResponse.class);
}
}

Python 调用示例​

import hashlib
import requests
import time

class ImApiClient:
def __init__(self, api_base_url: str, client_id: str, client_secret: str):
self.api_base_url = api_base_url
self.client_id = client_id
self.client_secret = client_secret

def confirm_install(
self,
workspace_id: int,
external_tenant_id: str,
installer_user_id: int,
timestamp: int,
sign: str
) -> dict:
"""确认应用安装"""
url = f"{self.api_base_url}/im-user/api/v1/oauth/install/confirm"

payload = {
"clientId": self.client_id,
"workspaceId": workspace_id,
"externalTenantId": external_tenant_id,
"installerUserId": installer_user_id,
"timestamp": timestamp,
"sign": sign
}

response = requests.post(url, json=payload)
result = response.json()

if result.get("code") != 200:
raise Exception(f"确认安装失败: {result.get('msg')}")

return result.get("data")

下一步​