Webhook

了解如何配置 webhook 以获取有关 EAS 构建和提交完成的警报。


EAS 可以通过 webhook 在你的构建或提交完成时立即提醒你。Webhook 需要为每个项目进行配置。例如,如果你希望同时收到 @johndoe/awesomeApp@johndoe/coolApp 的提醒,请在每个目录中运行以下命令:

🌐 EAS can alert you as soon as your build or submission is completed via a webhook. Webhooks need to be configured per project. For example, if you want to be alerted for both @johndoe/awesomeApp and @johndoe/coolApp, in each directory, run the command:

Terminal
eas webhook:create

运行后,系统会提示你选择 webhook 事件类型(除非你提供了 --event BUILD|SUBMIT 参数)。接下来,提供处理 HTTP POST 请求的 webhook URL(或使用 --url 标志指定它)。此外,如果你尚未通过 --secret 标志提供,还需要输入 webhook 签名密钥。它必须至少 16 个字符长,并将用于计算请求体的签名,我们会将该签名作为 expo-signature HTTP 头的值发送。你可以使用签名来验证 webhook 请求的真实性。

🌐 After running it, you'll be prompted to choose the webhook event type (unless you provide the --event BUILD|SUBMIT parameter). Next, provide the webhook URL (or specify it with the --url flag) that handles HTTP POST requests. Additionally, you'll have to input a webhook signing secret, if you have not already provided it with the --secret flag. It must be at least 16 characters long, and it will be used to calculate the signature of the request body which we send as the value of the expo-signature HTTP header. You can use the signature to verify a webhook request is genuine.

EAS 使用 HTTP POST 请求调用你的 webhook。所有数据都在请求正文中传递。EAS 以 JSON 对象的形式发送数据。如果 webhook 的响应 HTTP 状态码不在 200-399 范围内,系统会尝试再次发送,并采用指数退避策略。

🌐 EAS calls your webhook using an HTTP POST request. All the data is passed in the request body. EAS sends the data as a JSON object. If the webhook responds with an HTTP status code outside of the 200-399 range, delivery will be attempted a few more times with exponential back-off.

此外,我们会发送一个 expo-signature HTTP 头,其中包含负载的哈希签名。你可以使用此签名来验证请求的真实性。该签名是请求正文的十六进制编码 HMAC-SHA1 摘要,使用你的 webhook 密钥作为 HMAC 密钥。

🌐 Additionally, we send an expo-signature HTTP header with the hash signature of the payload. You can use this signature to verify the authenticity of the request. The signature is a hex-encoded HMAC-SHA1 digest of the request body, using your webhook secret as the HMAC key.

如果你想在本地测试上述 webhook,你可以使用像 ngrok 这样的服务,通过隧道转发 localhost:8080,并使用 ngrok 提供的 URL 公开访问它。

你始终可以通过运行命令来更改你的 webhook URL 和/或 webhook 密钥:

🌐 You can always change your webhook URL and/or webhook secret by running command:

Terminal
eas webhook:update --id WEBHOOK_ID

你可以通过运行以下命令找到 webhook ID:

🌐 You can find the webhook ID by running the command:

Terminal
eas webhook:list

如果你希望我们停止向你的 Webhook 发送请求,请运行以下命令并从列表中选择 Webhook:

🌐 If you want us to stop sending requests to your webhook, run the command below and choose the webhook from the list:

Terminal
eas webhook:delete

Webhook 负载

🌐 Webhook payload

构建 webhook 负载

构建 webhook 负载可能如下例所示:

🌐 The build webhook payload may look as the example below:

{ "id": "147a3212-49fd-446f-b4e3-a6519acf264a", "accountName": "dsokal", "projectName": "example", "buildDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/147a3212-49fd-446f-b4e3-a6519acf264a", "parentBuildId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for build retries "appId": "bc0a82de-65a5-4497-ad86-54ff1f53edf7", "initiatingUserId": "d1041496-1a59-423a-8caf-479bb978203a", "cancelingUserId": null, // available for canceled builds "platform": "android", // or "ios" "status": "errored", // or: "finished", "canceled" "artifacts": { "buildUrl": "https://expo.dev/artifacts/eas/wyodu9tua2ZuKKiaJ1Nbkn.aab", // available for successful builds "logsS3KeyPrefix": "production/f9609423-5072-4ea2-a0a5-c345eedf2c2a" }, "metadata": { "appName": "example", "username": "dsokal", "workflow": "managed", "appVersion": "1.0.2", "appBuildVersion": "123", "cliVersion": "0.37.0", "sdkVersion": "41.0.0", "buildProfile": "production", "distribution": "store", "appIdentifier": "com.expo.example", "gitCommitHash": "564b61ebdd403d28b5dc616a12ce160b91585b5b", "gitCommitMessage": "Add home screen", "runtimeVersion": "1.0.2", "channel": "default", // available for EAS Update "releaseChannel": "default", // available for legacy updates "reactNativeVersion": "0.60.0", "trackingContext": { "platform": "android", "account_id": "7c34cbf1-efd4-4964-84a1-c13ed297aaf9", "dev_client": false, "project_id": "bc0a82de-65a5-4497-ad86-54ff1f53edf7", "tracking_id": "a3fdefa7-d129-42f2-9432-912050ab0f10", "project_type": "managed", "dev_client_version": "0.6.2" }, "credentialsSource": "remote", "isGitWorkingTreeDirty": false, "message": "release build", // message attached to the build "runFromCI": false }, "metrics": { "memory": 895070208, "buildEndTimestamp": 1637747861168, "totalDiskReadBytes": 692224, "buildStartTimestamp": 1637747834445, "totalDiskWriteBytes": 14409728, "cpuActiveMilliseconds": 12117.540078, "buildEnqueuedTimestamp": 1637747792476, "totalNetworkEgressBytes": 355352, "totalNetworkIngressBytes": 78781667 }, // available for failed builds "error": { "message": "Unknown error. Please see logs.", "errorCode": "UNKNOWN_ERROR" }, "createdAt": "2021-11-24T09:53:01.155Z", "enqueuedAt": "2021-11-24T09:53:01.155Z", "provisioningStartedAt": "2021-11-24T09:54:01.155Z", "workerStartedAt": "2021-11-24T09:54:11.155Z", "completedAt": "2021-11-24T09:57:42.715Z", "updatedAt": "2021-11-24T09:57:42.715Z", "expirationDate": "2021-12-24T09:53:01.155Z", "priority": "high", // or: "normal", "low" "resourceClass": "android-n2-1.3-12", "actualResourceClass": "android-n2-1.3-12", "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled builds }
提交 webhook 负载

提交 webhook 的负载可能如下示例所示:

🌐 The submit webhook payload may look as the example below:

{ "id": "0374430d-7776-44ad-be7d-8513629adc54", "accountName": "dsokal", "projectName": "example", "submissionDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/0374430d-7776-44ad-be7d-8513629adc54", "parentSubmissionId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for submission retries "appId": "23c0e405-d282-4399-b280-5689c3e1ea85", "archiveUrl": "http://archive.url/abc.apk", "initiatingUserId": "7bee4c21-3eaa-4011-a0fd-3678b6537f47", "cancelingUserId": null, // available for canceled submissions "turtleBuildId": "8c84111e-6d39-449c-9895-071d85fd3e61", // available when submitting a build from EAS "platform": "android", // or "ios" "status": "errored", // or: "finished", "canceled" "submissionInfo": { // available for failed submissions "error": { "message": "Android version code needs to be updated", "errorCode": "SUBMISSION_SERVICE_ANDROID_OLD_VERSION_CODE_ERROR" }, "logsUrl": "https://submission-service-logs.s3-us-west-1.amazonaws.com/production/submission_728aa20b-f7a9-4da7-9b64-39911d427b19.txt" }, "createdAt": "2021-11-24T10:15:32.822Z", "updatedAt": "2021-11-24T10:17:32.822Z", "completedAt": "2021-11-24T10:17:32.822Z", "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled submissions }

Webhook 服务器

🌐 Webhook server

以下是如何实现服务器的示例:

🌐 Here's an example of how you can implement your server:

server.js
const crypto = require('crypto'); const express = require('express'); const bodyParser = require('body-parser'); const safeCompare = require('safe-compare'); const app = express(); app.use(bodyParser.text({ type: '*/*' })); app.post('/webhook', (req, res) => { const expoSignature = req.headers['expo-signature']; // process.env.SECRET_WEBHOOK_KEY has to match SECRET value set with `eas webhook:create` command const hmac = crypto.createHmac('sha1', process.env.SECRET_WEBHOOK_KEY); hmac.update(req.body); const hash = `sha1=${hmac.digest('hex')}`; if (!safeCompare(expoSignature, hash)) { res.status(500).send("Signatures didn't match!"); } else { // Do something here. For example, send a notification to Slack! // console.log(req.body); res.send('OK!'); } }); app.listen(8080, () => console.log('Listening on port 8080'));