# Expo build finished notifications on your phone > Get a High-priority push notification when an EAS build fails, and a quiet one when it's ready or canceled. - Company: Expo (https://www.justpush.io/recipes/expo) - Event: `BUILD` (BUILD) - Tags: Mobile apps, Builds & CI - Install: https://studio.justpush.io/recipes/expo/build - Web page: https://www.justpush.io/recipes/expo/build ## Setup 1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL. 2. In your project's directory, run eas webhook:create and choose the BUILD event. 3. Paste your webhook URL (shown in Studio after install) as the webhook URL. 4. Enter any signing secret of at least 16 characters. EAS requires one, but JustPush doesn't check it yet. 5. Webhooks are per project: repeat this in every project you want to hear about. 6. Run this in your project's directory (it needs EAS CLI and a logged-in account): 7. ``` 8. eas webhook:create --event BUILD --url "your webhook URL (shown in Studio after install)" 9. ``` 10. EAS asks for a signing secret of at least 16 characters; any random value works. Webhooks are set per project, so repeat this in each project. 11. You get a push for every build that finishes: High priority when it errors, Low when it's ready or canceled. A failed build shows EAS's error message; tap **View build** for the full logs. 12. EAS signs each request with `expo-signature` (HMAC-SHA1), but JustPush doesn't check that signature yet, so keep your endpoint URL private. ## Code Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing. ```js // Expo → EAS build finished, errored or canceled // Fires when an EAS Build completes, from a webhook created with `eas webhook:create --event BUILD`. // Docs: https://docs.expo.dev/eas/webhooks/ // Final build statuses: failures are loud, a finished build is a quiet "it's ready". const STATUSES = { errored: { emoji: "❌", verb: "build failed", priority: 1 }, // needs you soon: fix and rebuild finished: { emoji: "✅", verb: "build ready", priority: -1 }, // good news, no action needed canceled: { emoji: "⏹️", verb: "build canceled", priority: -1 }, // someone stopped it on purpose } const PLATFORMS = { ios: "iOS", android: "Android", } // Look a key up in a table without tripping over inherited names like "toString". function pick(table, key, fallback) { return typeof key === "string" && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback } // Treat blank or non-string values as missing. function text(value) { if (typeof value === "number" && Number.isFinite(value)) return String(value) return typeof value === "string" && value.trim() ? value.trim() : null } // Only link to real http(s) URLs. function link(url, cta) { return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : [] } function handleRequest(request) { const body = request.body && typeof request.body === "object" ? request.body : {} // Guard: EAS sends no event header, so recognise a build by its shape. Submission // webhooks have submissionDetailsPageUrl instead and are handled by expo/submission. if (!body.buildDetailsPageUrl || body.submissionDetailsPageUrl || body.submissionInfo) { console.log("Not an EAS build webhook, skipping") return null } const status = pick(STATUSES, body.status, null) if (!status) { console.log(`Skipping EAS build status "${body.status ?? "unknown"}"`) return null } const meta = body.metadata && typeof body.metadata === "object" ? body.metadata : {} const app = text(meta.appName) ?? text(body.projectName) ?? "Your app" const platform = pick(PLATFORMS, body.platform, null) // "1.4.0 (57)": the app version plus the build number or version code. const version = text(meta.appVersion) ? text(meta.appVersion) + (text(meta.appBuildVersion) ? ` (${text(meta.appBuildVersion)})` : "") : null // Failed builds explain why; others show the commit (or the message attached to the build). const detail = body.status === "errored" ? text(body.error?.message) ?? "See the build logs for details" : text(meta.gitCommitMessage)?.split("\n")[0] ?? text(meta.message) const message = [text(meta.buildProfile), version, detail].filter(Boolean).join(" · ") return { title: `${status.emoji} ${app} ${platform ? `${platform} ` : ""}${status.verb}`, message: message || `EAS build ${body.status}`, topic: "Expo", priority: status.priority, buttons: [ ...link(body.buildDetailsPageUrl, "View build"), ...(body.status === "finished" ? link(body.artifacts?.buildUrl, "Download") : []), ], } } ``` ## Expo BUILD webhook payload (sample) ```json { "id": "147a3212-49fd-446f-b4e3-a6519acf264a", "appId": "bc0a82de-65a5-4497-ad86-54ff1f53edf7", "error": { "message": "The \"Run fastlane\" step failed because of an error in the Xcode build process.", "errorCode": "XCODE_BUILD_ERROR" }, "status": "errored", "metadata": { "appName": "Acme Shop", "username": "janedoe", "workflow": "managed", "runFromCI": false, "appVersion": "1.4.0", "sdkVersion": "53.0.0", "buildProfile": "production", "distribution": "store", "appIdentifier": "com.example.acmeshop", "gitCommitHash": "564b61ebdd403d28b5dc616a12ce160b91585b5b", "appBuildVersion": "57", "gitCommitMessage": "Add checkout screen" }, "platform": "ios", "priority": "high", "artifacts": { "logsS3KeyPrefix": "production/f9609423-5072-4ea2-a0a5-c345eedf2c2a" }, "createdAt": "2026-09-24T09:53:01.155Z", "updatedAt": "2026-09-24T10:07:42.715Z", "accountName": "acme", "completedAt": "2026-09-24T10:07:42.715Z", "projectName": "acme-shop", "cancelingUserId": null, "initiatingUserId": "d1041496-1a59-423a-8caf-479bb978203a", "buildDetailsPageUrl": "https://expo.dev/accounts/acme/projects/acme-shop/builds/147a3212-49fd-446f-b4e3-a6519acf264a" } ``` ## FAQ ### Does this work on iPhone and Android? Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification. ### Do I need to write code? No. Install the recipe in Studio, paste your webhook URL into Expo and you are done. The code is there if you want to change the text, the sound or the buttons. ### Can I change what the notification says? Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save. ### What does it cost? JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.