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