Bitrise build finished notifications on your phone.

Get a High-priority push notification when a Bitrise build fails, and optionally a quiet one when it passes.

build/finishedCI/CDBuilds & CI
How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into Bitrise

Add it as a webhook for Build.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Bitrise build webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In Bitrise, open your project and go to Project settings > Integrations > Webhooks.
  3. Under Outgoing webhooks, click Add webhook and use the URL from the setup notes below.
  4. Choose Select individual events, tick the Build events, and click Create webhook.
  5. Use this as the outgoing webhook's URL, replacing the app name with your own:
  6. ```
  7. your webhook URL?app=Acme%20Shophttps://••••••••/•••••••• your personal URL, shown after install
  8. ```
  9. Bitrise's payload has no app name, so `app` is what the notification calls your project (`%20` is a space). Without it the push just says "Build #312 failed".
  10. Only failed builds (High priority) and builds aborted with a failure, such as a timeout (Normal), push by default. Add `&all=1` to also get a quiet push when a build passes:
  11. ```
  12. your webhook URL?app=Acme%20Shop&all=1
  13. ```
  14. Tick only the build events. Bitrise also sends a webhook when a build starts (`build/triggered`); the recipe ignores it, as it does pipeline events.
  15. Bitrise doesn't sign outgoing webhooks, and JustPush can't check custom headers, so keep your endpoint URL private. If a delivery goes missing, you can inspect and redeliver it from the webhook's **Recent deliveries**.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// Bitrise → build finished
2// Fires when a Bitrise build ends (Bitrise-Event-Type: build/finished). Failures push by
3// default; add ?all=1 to the webhook URL to also hear about passing and aborted-OK builds.
4// Docs: https://docs.bitrise.io/en/bitrise-platform/integrations/webhooks/adding-outgoing-webhooks.html
5
6// build_status values, as in the Bitrise API's build list.
7const STATUSES = {
8 1: { emoji: "✅", verb: "passed", priority: -1, always: false }, // good news, only with ?all=1
9 2: { emoji: "❌", verb: "failed", priority: 1, always: true }, // broken build: needs you soon
10 3: { emoji: "⏹️", verb: "was aborted", priority: 0, always: true }, // aborted with failure (e.g. timed out or stopped)
11 4: { emoji: "⏹️", verb: "was aborted", priority: -1, always: false }, // aborted with success, only with ?all=1
12}
13
14// Look a key up in a table without tripping over inherited names like "toString".
15function pick(table, key, fallback) {
16 const k = String(key)
17 return Object.prototype.hasOwnProperty.call(table, k) ? table[k] : fallback
18}
19
20// Header names can arrive in any case.
21function header(request, name) {
22 const headers = request.headers || {}
23 const key = Object.keys(headers).find((k) => k.toLowerCase() === name)
24 const value = key ? headers[key] : null
25 return Array.isArray(value) ? value[0] : value
26}
27
28// Treat blank or non-string values as missing.
29function text(value) {
30 if (typeof value === "number" && Number.isFinite(value)) return String(value)
31 return typeof value === "string" && value.trim() ? value.trim() : null
32}
33
34function handleRequest(request) {
35 const body = request.body && typeof request.body === "object" ? request.body : {}
36 const event = header(request, "bitrise-event-type")
37
38 // Guard: only build/finished. The header decides when it's there; without it (Studio's
39 // Test runner) a payload with a build slug and a final build_status counts.
40 const looksFinished = typeof body.build_slug === "string" && Number(body.build_status) > 0
41 if (event ? event !== "build/finished" : !looksFinished) {
42 console.log(`Ignoring Bitrise "${event ?? "unknown"}" event`)
43 return null
44 }
45
46 // ?all=1 on the webhook URL also sends quiet pushes for passing builds.
47 const all = ["1", "true", "yes"].includes(String(request.query?.all ?? "").toLowerCase())
48 const status = pick(STATUSES, body.build_status, null)
49 if (!status || (!status.always && !all)) {
50 console.log(`Skipping Bitrise build_status ${body.build_status ?? "unknown"}`)
51 return null
52 }
53
54 // The payload has no app name, so it comes from ?app= on the webhook URL.
55 const app = text(request.query?.app)
56 const number = text(body.build_number)
57 const build = number ? `build #${number}` : "build"
58
59 // "deploy · feature/checkout → main (PR #88)" or "deploy · tag v1.4.0"
60 const git = body.git && typeof body.git === "object" ? body.git : {}
61 const pr = Number(git.pull_request_id) > 0 ? ` (PR #${git.pull_request_id})` : ""
62 const where = text(git.tag)
63 ? `tag ${text(git.tag)}`
64 : text(git.src_branch)
65 ? text(git.src_branch) + (pr && text(git.dst_branch) ? ` → ${text(git.dst_branch)}` : "") + pr
66 : null
67
68 const slug = /^[A-Za-z0-9-]+$/.test(body.build_slug) ? body.build_slug : null
69
70 return {
71 title: `${status.emoji} ${app ? `${app} ${build}` : build.charAt(0).toUpperCase() + build.slice(1)} ${status.verb}`,
72 message: [text(body.build_triggered_workflow), where].filter(Boolean).join(" · ") || "Bitrise build finished",
73 topic: "Bitrise",
74 priority: status.priority,
75 buttons: slug ? [{ cta: "View build", url: `https://app.bitrise.io/build/${slug}` }] : [],
76 }
77}
Payload

The Bitrise build/finished webhook

This is what Bitrise sends to your URL for build. It is a sample, trimmed to the fields recipes use.

build/finished · sample.json
1{
2 "git": {
3 "tag": null,
4 "provider": "github",
5 "dst_branch": "main",
6 "src_branch": "feature/checkout",
7 "pull_request_id": 88
8 },
9 "app_slug": "de7829a7317d976e",
10 "build_slug": "58f4da148d884d68",
11 "build_number": 312,
12 "build_status": 2,
13 "build_triggered_workflow": "deploy-testflight"
14}
FAQ

Bitrise build finished notifications: questions

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 Bitrise 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.

Ready when you are

Bitrise on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.