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