xref: /expo/docs/pages/eas/webhooks.mdx (revision cedf99eb)
1---
2title: Webhooks
3description: Learn how to configure webhooks to get alerts on EAS Build and EAS submit completion.
4---
5
6import { Collapsible } from '~/ui/components/Collapsible';
7
8EAS can alert you as soon as your build or submission has completed via a webhook. Webhooks need to be configured per-project, so if you want to be alerted for both `@johndoe/awesomeApp` and `@johndoe/coolApp`, you need to run `eas webhook:create` in each directory.
9
10After running `eas webhook:create`, 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 (example code below).
11
12EAS 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.
13
14Additionally, 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.
15
16> If you want to test the above webhook locally, you have to use a service like [ngrok](https://ngrok.com/docs) to forward `localhost:8080` via a tunnel and make it publicly accessible with the URL `ngrok` gives you.
17
18You can always change your webhook URL and/or webhook secret using `eas webhook:update --id WEBHOOK_ID`. You can find the webhook ID by running `eas webhook:list`. If you would like us to stop sending requests to your webhook, run `eas webhook:delete` and choose the webhook from the list.
19
20## Webhook payload
21
22<Collapsible summary="Build webhook payload">
23
24The build webhook payload looks something like this:
25
26```json
27{
28  "id": "147a3212-49fd-446f-b4e3-a6519acf264a",
29  "accountName": "dsokal",
30  "projectName": "example",
31  "buildDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/147a3212-49fd-446f-b4e3-a6519acf264a",
32  "parentBuildId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for build retries
33  "appId": "bc0a82de-65a5-4497-ad86-54ff1f53edf7",
34  "initiatingUserId": "d1041496-1a59-423a-8caf-479bb978203a",
35  "cancelingUserId": null, // available for canceled builds
36  "platform": "android", // or "ios"
37  "status": "errored", // or: "finished", "canceled"
38  "artifacts": {
39    "buildUrl": "https://expo.dev/artifacts/eas/wyodu9tua2ZuKKiaJ1Nbkn.aab", // available for successful builds
40    "logsS3KeyPrefix": "production/f9609423-5072-4ea2-a0a5-c345eedf2c2a"
41  },
42  "metadata": {
43    "appName": "example",
44    "username": "dsokal",
45    "workflow": "managed",
46    "appVersion": "1.0.2",
47    "appBuildVersion": "123",
48    "cliVersion": "0.37.0",
49    "sdkVersion": "41.0.0",
50    "buildProfile": "production",
51    "distribution": "store",
52    "appIdentifier": "com.expo.example",
53    "gitCommitHash": "564b61ebdd403d28b5dc616a12ce160b91585b5b",
54    "gitCommitMessage": "Add home screen",
55    "runtimeVersion": "1.0.2",
56    "channel": "default", // available for EAS Update
57    "releaseChannel": "default", // available for legacy updates
58    "reactNativeVersion": "0.60.0",
59    "appBuildVersion": "6",
60    "trackingContext": {
61      "platform": "android",
62      "account_id": "7c34cbf1-efd4-4964-84a1-c13ed297aaf9",
63      "dev_client": false,
64      "project_id": "bc0a82de-65a5-4497-ad86-54ff1f53edf7",
65      "tracking_id": "a3fdefa7-d129-42f2-9432-912050ab0f10",
66      "project_type": "managed",
67      "dev_client_version": "0.6.2"
68    },
69    "credentialsSource": "remote",
70    "isGitWorkingTreeDirty": false,
71    "message": "release build", // message attached to the build
72    "runFromCI": false
73  },
74  "metrics": {
75    "memory": 895070208,
76    "buildEndTimestamp": 1637747861168,
77    "totalDiskReadBytes": 692224,
78    "buildStartTimestamp": 1637747834445,
79    "totalDiskWriteBytes": 14409728,
80    "cpuActiveMilliseconds": 12117.540078,
81    "buildEnqueuedTimestamp": 1637747792476,
82    "totalNetworkEgressBytes": 355352,
83    "totalNetworkIngressBytes": 78781667
84  },
85  // available for failed builds
86  "error": {
87    "message": "Unknown error. Please see logs.",
88    "errorCode": "UNKNOWN_ERROR"
89  },
90  "createdAt": "2021-11-24T09:53:01.155Z",
91  "enqueuedAt": "2021-11-24T09:53:01.155Z",
92  "provisioningStartedAt": "2021-11-24T09:54:01.155Z",
93  "workerStartedAt": "2021-11-24T09:54:11.155Z",
94  "completedAt": "2021-11-24T09:57:42.715Z",
95  "updatedAt": "2021-11-24T09:57:42.715Z",
96  "expirationDate": "2021-12-24T09:53:01.155Z",
97  "priority": "high", // or: "normal", "low"
98  "resourceClass": "android-n2-1.3-12",
99  "actualResourceClass": "android-n2-1.3-12",
100  "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled builds
101}
102```
103
104</Collapsible>
105
106<Collapsible summary="Submit webhook payload">
107
108The submit webhook payload looks something like this:
109
110```json
111{
112  "id": "0374430d-7776-44ad-be7d-8513629adc54",
113  "accountName": "dsokal",
114  "projectName": "example",
115  "submissionDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/0374430d-7776-44ad-be7d-8513629adc54",
116  "parentSubmissionId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for submission retries
117  "appId": "23c0e405-d282-4399-b280-5689c3e1ea85",
118  "archiveUrl": "http://archive.url/abc.apk",
119  "initiatingUserId": "7bee4c21-3eaa-4011-a0fd-3678b6537f47",
120  "cancelingUserId": null, // available for canceled submissions
121  "turtleBuildId": "8c84111e-6d39-449c-9895-071d85fd3e61", // available when submitting a build from EAS
122  "platform": "android", // or "ios"
123  "status": "errored", // or: "finished", "canceled"
124  "submissionInfo": {
125    // available for failed submissions
126    "error": {
127      "message": "Android version code needs to be updated",
128      "errorCode": "SUBMISSION_SERVICE_ANDROID_OLD_VERSION_CODE_ERROR"
129    },
130    "logsUrl": "https://submission-service-logs.s3-us-west-1.amazonaws.com/production/submission_728aa20b-f7a9-4da7-9b64-39911d427b19.txt"
131  },
132  "createdAt": "2021-11-24T10:15:32.822Z",
133  "updatedAt": "2021-11-24T10:17:32.822Z",
134  "completedAt": "2021-11-24T10:17:32.822Z",
135  "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled submissions
136}
137```
138
139</Collapsible>
140
141## Webhook server
142
143Here's an example of how you can implement your server:
144
145```javascript
146const crypto = require('crypto');
147const express = require('express');
148const bodyParser = require('body-parser');
149const safeCompare = require('safe-compare');
150
151const app = express();
152app.use(bodyParser.text({ type: '*/*' }));
153app.post('/webhook', (req, res) => {
154  const expoSignature = req.headers['expo-signature'];
155  // process.env.SECRET_WEBHOOK_KEY has to match SECRET value set with `eas webhook:create` command
156  const hmac = crypto.createHmac('sha1', process.env.SECRET_WEBHOOK_KEY);
157  hmac.update(req.body);
158  const hash = `sha1=${hmac.digest('hex')}`;
159  if (!safeCompare(expoSignature, hash)) {
160    res.status(500).send("Signatures didn't match!");
161  } else {
162    // do something here, like send a notification to Slack!
163    // console.log(req.body);
164    res.send('OK!');
165  }
166});
167app.listen(8080, () => console.log('Listening on port 8080'));
168```
169