# Expo (EAS) Webhooks

## When to Use This Skill

* How do I receive Expo EAS Build or EAS Submit webhooks?
* How do I verify the Expo `expo-signature` webhook header?
* Why does my Expo webhook signature verification fail?
* How do I tell a build payload from a submit payload when there is no event type field?
* How do I handle `finished`, `errored`, and `canceled` build and submission statuses?

## Verification (core)

EAS signs the **raw request body** with HMAC-SHA1, keyed with the secret you passed to `eas webhook:create --secret` (at least 16 characters). The `expo-signature` header is `sha1=<hex digest>`. There is no timestamp and no message ID.

```javascript
const crypto = require('crypto');

function verifyExpoSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = 'sha1=' + crypto.createHmac('sha1', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}

```

In Express, read the body with `express.raw({ type: '*/*' })` so verification works whatever `Content-Type` EAS sends. Parse JSON only after the signature passes.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/expo-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/expo-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/expo-webhooks/examples/fastapi/).

## Webhook Types and Statuses

EAS has **no event-type field** in the payload. Whether a webhook is BUILD or SUBMIT is set when you run `eas webhook:create --event BUILD|SUBMIT`, so tell payloads apart by their fields:

| Webhook | Identifying fields | Notes |
| --- | --- | --- |
| `BUILD` | `buildDetailsPageUrl`, `artifacts`, `metadata` | `artifacts.buildUrl` is present for successful builds; `error` for failed ones |
| `SUBMIT` | `submissionDetailsPageUrl`, `submissionInfo` | `archiveUrl`, `turtleBuildId`; `submissionInfo.error` for failed submissions |

Both carry `id`, `accountName`, `projectName`, `platform` (`ios` or `android`), and `status`:

| `status` | Meaning |
| --- | --- |
| `finished` | Build or submission succeeded |
| `errored` | Failed; see `error` (build) or `submissionInfo.error` (submit) |
| `canceled` | Canceled; `cancelingUserId` is set |

> **For full payloads**, see [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/expo-webhooks/references/overview.md).

## Important Headers

| Header | Description |
| --- | --- |
| `expo-signature` | `sha1=` followed by the hex HMAC-SHA1 of the raw body |
| `Content-Type` | JSON; do not rely on it for body parsing |

## Environment Variables

```bash
EXPO_WEBHOOK_SECRET=your_webhook_secret   # the value passed to eas webhook:create --secret

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 expo --path /webhooks/expo

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/expo-webhooks/references/overview.md) - BUILD and SUBMIT payloads, statuses, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/expo-webhooks/references/setup.md) - Create, update, list, and delete webhooks with the EAS CLI
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/expo-webhooks/references/verification.md) - expo-signature details and debugging