---
topic: alerts
connector: "None yet: send the person to the app's Settings → Alerts"
api: "https://ship.embarko.ai/api/apps/{app-name}/alerts"
needs_human: "An M91 link comes from the person's phone: ask them for it, never invent one."
requires: [status]
---

[← Guide home](https://embarko.ai/docs)

# Crash alerts

**On this page:** Over HTTP, With the connector, Examples, Limits, Errors, What to tell the person

> Plain words unless the person is a developer. Never invent a link, name or code. [Rules](https://embarko.ai/docs/prerequisites) Needs first: [status](https://embarko.ai/docs/status).

Embarko emails the person who created the app when it goes down or keeps crashing, and again when it works again. Nothing to set up. You can also add more emails, a Slack or Discord channel, a webhook, or an M91 channel that rings phones; send a test to each; turn on quieter warnings; or mute alerts for an hour or a day.

**Connector:** None yet: send the person to the app's Settings → Alerts

**Done when:** The person's alerts go where they asked, a test reached each new place, and you know whether the app is having an outage.

**Over HTTP**

1. Every call takes the deploy token as Authorization: Bearer. Reading alerts and past outages needs view access to the app (Can view or more); changing alerts needs Full access.
2. See where alerts go: GET https://ship.embarko.ai/api/apps/{app-name}/alerts. owner.email is always told; channels are the extra places.
3. Add a place: POST https://ship.embarko.ai/api/apps/{app-name}/alerts/channels with {"type":"email","target":"name@company.com"}, {"type":"webhook","target":"<Slack, Discord or any https URL>"} or {"type":"m91","target":"<the M91 channel link>"}.
4. For M91, ask the person to: 1. install M91 (iPhone https://apps.apple.com/app/id6759090472, Android https://play.google.com/store/apps/details?id=com.msg91.m91); 2. create a channel and add whoever should answer; 3. tap ✈ on the channel, copy the link and paste it to you. Everyone in the channel must install M91 and accept the invite.
5. Test it: POST https://ship.embarko.ai/api/apps/{app-name}/alerts/channels/{channelId}/test (use "owner" as channelId for the creator's email). The reply's delivered is how many phones an M91 test reached; it is null for email and webhooks. Ask the person to confirm it arrived.
6. Change settings: PATCH https://ship.embarko.ai/api/apps/{app-name}/alerts with any of {"mute":"1h"|"24h"|"off"} (a pause that ends by itself), {"enabled":false|true} (off until someone turns it back on), {"warnings":true|false} (also out of memory and failed starts). Both mute and enabled:false silence every place, warnings included. For a pause of a few hours, use mute 24h and turn it off when they say so.
7. Remove a place: DELETE https://ship.embarko.ai/api/apps/{app-name}/alerts/channels/{channelId}. Pause just one place: PATCH the same URL with {"enabled":false}; {"enabled":true} turns it back on, also after it was turned off for failing.
8. Read past outages: GET https://ship.embarko.ai/api/apps/{app-name}/incidents, newest first. status "open" is happening now. cause "app" is the app's own code: read its logs, fix and redeploy. cause "platform" is Embarko's side: nothing to fix in the app for it. Tell the person so (see platform under What to tell the person). Do not redeploy or roll back because of it; a fix for a separate cause "app" problem can still go out.

**With the connector**

1. No HTTP (connector without a token, chat-only, upload page): you cannot read or change alerts. Send the person to the app's settings: https://embarko.ai/app/projects/{app.id}/settings when you know app.id (from the deploy reply, or "appId" in embarko.json); otherwise https://embarko.ai/app, open the app, then Settings. The Alerts section there adds places, sends tests, mutes and lists past outages.
2. Or ask for a deploy token (https://embarko.ai/app/tokens) and use the HTTP steps, if you can reach ship.embarko.ai.

**Example: Add a Slack channel**

```bash
curl -sS -X POST 'https://ship.embarko.ai/api/apps/my-app/alerts/channels' --oauth2-bearer "$DEPLOY_TOKEN" -H 'Content-Type: application/json' -d '{"type":"webhook","target":"https://hooks.slack.com/services/..."}'
```

Reply 201:

```json
{"channel":{"id":"cm…","type":"webhook","label":"hooks.slack.com","format":"slack","enabled":true,"disabledReason":null,"lastDeliveryAt":null,"lastError":null},"signingSecret":null}
```

**Example: Where alerts go**

```bash
curl -sS 'https://ship.embarko.ai/api/apps/my-app/alerts' --oauth2-bearer "$DEPLOY_TOKEN"
```

Reply 200:

```json
{"enabled":true,"warnings":false,"mutedUntil":null,"owner":{"email":"rita@acme.dev"},"channels":[{"id":"cm…","type":"m91","label":"Shop on-call","format":null,"enabled":true}],"canEdit":true}
```

**Example: Recent outages**

```bash
curl -sS 'https://ship.embarko.ai/api/apps/my-app/incidents' --oauth2-bearer "$DEPLOY_TOKEN"
```

Reply 200:

```json
{"incidents":[{"id":"cm…","kind":"crash_loop","status":"resolved","cause":"app","reason":"Exited with code 1.","openedAt":"2026-10-28T11:19:25.000Z","resolvedAt":"2026-10-28T11:29:35.000Z","resolution":"recovered"}]}
```

**Example: Test a place**

```bash
curl -sS -X POST 'https://ship.embarko.ai/api/apps/my-app/alerts/channels/owner/test' --oauth2-bearer "$DEPLOY_TOKEN"
```

Reply 200:

```json
{"ok":true,"delivered":null}
```

**Example: Webhook payload**

```json
{"event":"app.crash_loop","app":{"id":"cmh1q2w3e0001abcdmyapp","name":"my-app","url":"https://my-app.embarko.app"},"incident":{"id":"cmh9k2x7a0003abcdincdnt","kind":"crash_loop","cause":"app","openedAt":"2026-10-28T11:19:25.000Z","resolvedAt":null,"crashCount":3,"exitCode":1,"oomKilled":false},"title":"my-app keeps crashing","message":"It has crashed 3 times in a few minutes. It is being restarted each time, so some visitors get errors. Exited with code 1.","reason":"Exited with code 1.","links":{"dashboard":"https://embarko.ai/app/projects/cmh1q2w3e0001abcdmyapp","fix":"https://embarko.ai/fix#…"},"sentAt":"2026-10-28T11:19:31.000Z"}
```

Sent to a generic webhook. A test has event "test", incident null, and title "Test from Embarko: alerts for {app} arrive here"; it is signed like any other message. Slack and Discord get that title and message as plain text. links.fix is set only for app.down and app.crash_loop.

**Limits**

- One message when a problem starts and one when it ends, never one per crash. Stops Embarko makes (sleeping when idle, deploys) are never outages.
- Muting silences every alert for this app, warnings included, in every place, the creator's email too. A problem that starts while muted sends no "working again" message either.
- Up to 10 places per app. The creator's email does not count towards the 10. A saved webhook or M91 link is never shown again, only its label (the host or the M91 channel's name).
- A generic webhook gets a signing secret once, in the add reply: give it to whoever runs the receiver. Each message is JSON with event app.down, app.crash_loop, app.oom, app.wake_failed, app.recovered, app.platform_outage or app.platform_recovered, and carries X-Embarko-Delivery (drop repeats with the same value) and X-Embarko-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with the signing secret>. Slack and Discord addresses get a plain message instead.
- A webhook delivery succeeds on any 2xx reply within 10 seconds. Redirects are not followed and count as failed. A 404 or 410 means the address is gone, and that message is not retried.
- A failed message is tried again after 1 minute, 5 minutes, 30 minutes, then every 2 hours, for up to 24 hours. Every try has the same X-Embarko-Delivery, so keep seen values for 24 hours. t is the time of each try: rejecting a t more than 5 minutes from your clock is safe.
- A lost signing secret cannot be shown again or changed. Remove the place and add it again to get a new one.
- A place that fails 10 times in a row is turned off and the creator is emailed.
- Temporary (unclaimed) apps have no alerts until the app is kept.

**Errors**

| Code | Status | Retry | What to do |
| --- | --- | --- | --- |
| `invalid_email` | 400 | false | Ask the person for the right address. |
| `invalid_webhook` | 400 | false | The URL must be public https. Ask for the right one. |
| `invalid_m91_link` | 400 | false | Not an M91 channel link. Ask the person to copy it again from the ✈ on the channel. |
| `m91_link_rejected` | 400 | false | M91 refused the link (revoked or copied wrong). Ask for a fresh one. |
| `too_many_channels` | 400 | false | Remove one first. For more people than places left, suggest one shared group address or one Slack or M91 channel they all join. |
| `alert_test_unreached` | 502 | false | Nobody in the M91 channel can be reached: everyone must install M91 and accept the invite. Tell the person. |
| `alert_test_failed` | 502 | true | The message names why. Fix the address or try again. |
| `channel_not_found` | 404 | false | List the channels again with GET …/alerts. |
| `permission_denied` | 403 | false | Changing alerts needs Full access to the app. Tell the person in the reply's own words and stop: an Owner or Admin can give them Full access, or make the change in the app's Settings → Alerts (see team). |

**What to tell the person**

- failed: "Your app went down. I can see why in its logs and I'm fixing it now."
- platform: "Your app is down because of a problem on Embarko's side, not in your code. Nothing to change; Embarko is fixing it."

**Next:** Change where alerts go only when the person asks. For an open incident with cause "app", read the logs, fix the cause and redeploy. With cause "platform", tell the person it is Embarko's side; nothing in the app changes for it.

**Related:** [logs](https://embarko.ai/docs/logs) · [status](https://embarko.ai/docs/status) · [rollback](https://embarko.ai/docs/rollback) · [team](https://embarko.ai/docs/team)

---

[Guide home](https://embarko.ai/docs) · JSON: add .json