Railway deployment notifications on your phone.

Get a push notification when a Railway deployment succeeds, fails or crashes, loudest when a production service crashes.

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 Railway

Add it as a webhook for Deployment status.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Railway deployment status 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 Railway, open your project and go to Settings > Webhooks.
  3. Paste your webhook URL as the webhook URL and pick the Deployment status events.https://••••••••/•••••••• your personal URL, shown after install
  4. Click Save Webhook. It covers every service and environment in the project.
  5. In Railway, open the project's **Settings → Webhooks**, paste the URL above and click **Save Webhook**. If you pick events, choose the deployment status changes. The webhook covers every service and environment in the project.
  6. The recipe pushes for deployments that **succeed**, **fail**, **crash** or **run out of memory**, and for deploys that **need approval**. Building, deploying, removed and sleeping states stay quiet. Add `?failures=1` to only get crashes and failures:
  7. ```
  8. your webhook URL?failures=1
  9. ```
  10. An environment called `production` gets the loudest pushes: Highest for a crash, High for a failed deploy.
  11. Railway's **Test Webhook** button is sent from your browser and its payload isn't documented, so it may not produce a push. Trigger a redeploy to test instead.
  12. Railway doesn't sign webhooks. Keep your endpoint URL private.
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// Railway → deployment status changed
2// Fires when a Railway project webhook reports a deployment reaching a final state
3// (succeeded, failed, crashed, out of memory) or waiting for approval.
4// Docs: https://docs.railway.com/observability/webhooks
5
6// Deployment states (lowercased, from the "Deployment.<state>" type or the legacy
7// "status" field), their labels, and how loud to be in production vs elsewhere.
8const STATES = {
9 // A crashed service is down right now; in production that's the loudest push.
10 crashed: { emoji: "💥", verb: "crashed", production: 2, other: 1 },
11 oomkilled: { emoji: "💥", verb: "ran out of memory", production: 2, other: 1 },
12 // A failed deploy needs you soon; the previous deployment normally keeps running.
13 failed: { emoji: "❌", verb: "deploy failed", production: 1, other: 0 },
14 // Someone has to approve the deploy before it goes out.
15 needsapproval: { emoji: "⏸️", verb: "deploy needs approval", production: 0, other: 0 },
16 // Good news is routine.
17 success: { emoji: "✅", verb: "deployed", production: -1, other: -1 },
18 deployed: { emoji: "✅", verb: "deployed", production: -1, other: -1 },
19 redeployed: { emoji: "✅", verb: "redeployed", production: -1, other: -1 },
20}
21
22// States that count as problems, for ?failures=1.
23const PROBLEMS = ["crashed", "oomkilled", "failed"]
24
25// Look a key up in one of the tables above, ignoring inherited names like "toString".
26function pick(table, key, fallback) {
27 return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
28}
29
30// Only link to real http(s) URLs.
31function link(url, cta) {
32 return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : []
33}
34
35// A plain, non-empty string or null.
36function text(value) {
37 return typeof value === "string" && value.trim() ? value.trim() : null
38}
39
40function handleRequest(request) {
41 const body = request.body && typeof request.body === "object" ? request.body : {}
42 const type = text(body.type) ?? ""
43
44 // Current format: type "Deployment.crashed". Older webhooks sent type "DEPLOY"
45 // with the state in a top-level "status" (e.g. "CRASHED").
46 let state = null
47 if (/^deployment\./i.test(type)) state = type.slice("deployment.".length)
48 else if (type.toUpperCase() === "DEPLOY") state = text(body.status)
49 state = String(state ?? "").toLowerCase().replace(/[^a-z]/g, "")
50 const event = pick(STATES, state, null)
51
52 // Guard: only deployment states worth a push (building, deploying, removed … stay quiet).
53 if (!event) {
54 console.log(`Ignoring Railway "${type || "unknown"}" event`)
55 return null
56 }
57
58 // ?failures=1 on the webhook URL keeps only crashes and failures.
59 const failuresOnly = ["1", "true", "yes"].includes(String(request.query?.failures ?? "").toLowerCase())
60 if (failuresOnly && !PROBLEMS.includes(state)) {
61 console.log(`Skipping "${state}" (the URL has ?failures=1)`)
62 return null
63 }
64
65 // The current format nests names under "resource"; the legacy one had them at the top.
66 const resource = body.resource && typeof body.resource === "object" ? body.resource : body
67 const details = body.details && typeof body.details === "object" ? body.details : body.deployment?.meta ?? {}
68 const service = text(resource.service?.name) ?? text(resource.project?.name) ?? "Your service"
69 const environment = text(resource.environment?.name)
70 const production = /^prod(uction)?$/i.test(environment ?? "")
71
72 const commit = (text(details.commitMessage) ?? "").split("\n")[0]
73 const author = text(details.commitAuthor)
74 const message = [text(resource.project?.name), text(details.branch), commit, author ? `@${author}` : null].filter(Boolean).join(" · ")
75
76 // Link to the service in the dashboard when the IDs are there.
77 const projectId = text(resource.project?.id)
78 const serviceId = text(resource.service?.id)
79 const environmentId = text(resource.environment?.id)
80 const url = projectId && serviceId
81 ? `https://railway.com/project/${encodeURIComponent(projectId)}/service/${encodeURIComponent(serviceId)}` +
82 (environmentId ? `?environmentId=${encodeURIComponent(environmentId)}` : "")
83 : null
84
85 return {
86 title: `${event.emoji} ${service} ${event.verb}${environment ? ` (${environment})` : ""}`,
87 message: message || "Deployment update from Railway",
88 topic: "Railway",
89 priority: production ? event.production : event.other,
90 buttons: link(url, "Open in Railway"),
91 }
92}
Payload

The Railway Deployment.crashed webhook

This is what Railway sends to your URL for deployment status. It is a sample, trimmed to the fields recipes use.

Deployment.crashed · sample.json
1{
2 "type": "Deployment.crashed",
3 "details": {
4 "id": "8107edff-4b8e-44fc-b43a-04566e847a2a",
5 "branch": "main",
6 "source": "GitHub",
7 "status": "CRASHED",
8 "commitHash": "3f2a1bc9e7d84c5a9b0e6f1d2c3b4a5968778695",
9 "commitAuthor": "janedoe",
10 "commitMessage": "Add rate limiting to the API\n\nCloses #88"
11 },
12 "resource": {
13 "project": {
14 "id": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0",
15 "name": "acme-shop"
16 },
17 "service": {
18 "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
19 "name": "api"
20 },
21 "workspace": {
22 "id": "5c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
23 "name": "Acme"
24 },
25 "deployment": {
26 "id": "8107edff-4b8e-44fc-b43a-04566e847a2a"
27 },
28 "environment": {
29 "id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
30 "name": "production",
31 "isEphemeral": false
32 }
33 },
34 "severity": "ERROR",
35 "timestamp": "2026-09-29T08:32:10.311Z"
36}
FAQ

Railway deployment 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 Railway 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

Railway on your phone in two minutes.

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