# Heroku build failed notifications on your phone

> Get a High-priority push notification when a Heroku build fails, with a button to the build log, and optionally a quiet one when it succeeds.

- Company: Heroku (https://www.justpush.io/recipes/heroku)
- Event: `api:build` (api:build)
- Tags: Hosting & deployment, Builds & CI
- Install: https://studio.justpush.io/recipes/heroku/build-failed
- Web page: https://www.justpush.io/recipes/heroku/build-failed

## 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 the Heroku Dashboard, open your app and choose More > View webhooks, then create a webhook. Or use the CLI command in the setup notes below.
3. Paste your webhook URL (shown in Studio after install) as the URL and subscribe to the api:build event type.
4. Save. Each app needs its own webhook.
5. Subscribe the webhook to the **`api:build`** entity. In the Dashboard that's **More → View webhooks** on your app; with the Heroku CLI:
6. ```
7. heroku webhooks:add -i api:build -l notify -u your webhook URL (shown in Studio after install) -a your-app
8. ```
9. Only failed builds push by default. Add `?all=1` to the URL to also get a quiet push when a build succeeds:
10. ```
11. your webhook URL (shown in Studio after install)?all=1
12. ```
13. Heroku signs every delivery (`Heroku-Webhook-Hmac-SHA256`), 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
// Heroku → api:build (build failed)
// Fires when a Heroku app webhook subscribed to api:build reports a finished build.
// Only failed builds push, unless the webhook URL has ?all=1.
// Docs: https://devcenter.heroku.com/articles/webhook-events#api-build

// 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 : {}
    const data = body.data && typeof body.data === "object" ? body.data : {}
    const include = body.webhook_metadata?.event?.include

    // Guard: only build events.
    if (body.resource !== "build" && include !== "api:build") {
        console.log(`Ignoring Heroku "${include ?? body.resource ?? "unknown"}" event`)
        return null
    }

    // ?all=1 on the webhook URL also sends a quiet push for successful builds.
    const all = ["1", "true", "yes"].includes(String(request.query?.all ?? "").toLowerCase())
    const failed = data.status === "failed"
    if (!failed && !(all && data.status === "succeeded")) {
        console.log(`Skipping build with status "${data.status ?? "none"}"`)
        return null
    }

    const app = typeof data.app?.name === "string" && data.app.name ? data.app.name : null
    // source_blob.version is usually the Git commit SHA; show it short.
    const version = typeof data.source_blob?.version === "string" ? data.source_blob.version : ""
    const commit = /^[0-9a-f]{12,}$/i.test(version) ? version.slice(0, 7) : version
    const who = data.user?.email ?? body.actor?.email
    const message = [commit, who].filter((part) => typeof part === "string" && part).join(" · ")
    const url = app && typeof data.id === "string"
        ? `https://dashboard.heroku.com/apps/${encodeURIComponent(app)}/activity/builds/${encodeURIComponent(data.id)}`
        : null

    return {
        title: failed ? `❌ ${app ?? "Your app"} build failed` : `✅ ${app ?? "Your app"} built`,
        message: message || (failed ? "The build failed" : "The build succeeded"),
        topic: "Heroku",
        priority: failed ? 1 : -1, // High for a broken build, Low for the optional success pushes
        buttons: link(url, "View build log"),
    }
}
```

## Heroku api:build webhook payload (sample)

```json
{
  "id": "498b59ef-6791-4216-8000-8dc7b7e5e6fa",
  "data": {
    "id": "d1315ea5-4885-4288-bc99-a470a005bbcc",
    "app": {
      "id": "7c9e1f74-ad2c-4daf-91e5-f07bb7cafe90",
      "name": "acme-api"
    },
    "slug": null,
    "user": {
      "id": "7c1e3b25-40a1-416f-91a9-7cd01e517a28",
      "email": "jane@example.com"
    },
    "stack": "heroku-24",
    "status": "failed",
    "release": null,
    "buildpacks": [
      {
        "url": "heroku/nodejs"
      }
    ],
    "created_at": "2026-09-29T07:51:37Z",
    "updated_at": "2026-09-29T07:52:58Z",
    "source_blob": {
      "url": "https://example.com/source.tgz",
      "version": "3f2a1bc9e7d84c5a9b0e6f1d2c3b4a5968778695",
      "checksum": null
    },
    "output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
  },
  "actor": {
    "id": "7c1e3b25-40a1-416f-91a9-7cd01e517a28",
    "email": "jane@example.com"
  },
  "action": "update",
  "version": "application/vnd.heroku+json; version=3",
  "resource": "build",
  "sequence": null,
  "created_at": "2026-09-29T07:51:37+00:00",
  "updated_at": "2026-09-29T07:52:58+00:00",
  "published_at": "2026-09-29T07:52:58Z",
  "previous_data": [],
  "webhook_metadata": {
    "event": {
      "id": "498b59ef-6791-4216-8000-8dc7b7e5e6fa",
      "include": "api:build"
    },
    "attempt": {
      "id": "8a44f820-2354-489d-9a11-a793cbf49979"
    },
    "webhook": {
      "id": "b54e6a7e-d162-4bd7-ab42-1011582c19cc"
    },
    "delivery": {
      "id": "d244009a-670f-4340-88e9-789a4f9002d5"
    }
  }
}
```

## 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 Heroku 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.
