Skip to main content
Glama
tranzen21

storekit-verify-mcp

by tranzen21
README.md
# storekit-verify-mcp

**Decode and cryptographically verify Apple App Store Server Notifications V2 — as an MCP server and a CLI.**

Apple delivers subscription webhooks (`SUBSCRIBED`, `DID_RENEW`, `EXPIRED`, `REFUND`, …) as signed JWS payloads. Verifying them properly means checking the ES256 signature, walking a 3-certificate chain to a pinned Apple root, checking Apple's policy OIDs, and doing it again for the nested transaction and renewal payloads. Most servers either skip verification entirely or trust a decode. This tool does it right, and explains what each notification means while it's at it.

I built this logic for [Talli](https://talliapp.co), my live App Store app, where server-side webhook verification drives subscription entitlements. This is that experience packaged as a standalone tool.

## What it checks

- ✅ ES256 signature of the outer `signedPayload` **and** the nested `signedTransactionInfo` / `signedRenewalInfo`
- ✅ Full x5c chain: leaf → intermediate → root, each link's signature verified
- ✅ Root pinned to **Apple Root CA – G3** (bundled, SHA-256 fingerprint documented in source)
- ✅ Apple's policy OIDs on the leaf (receipt signing) and intermediate (WWDR)
- ✅ Certificate validity windows at the payload's `signedDate`
- ✅ Optional `bundleId` and `environment` (Sandbox/Production) assertions

## MCP server

Add to Claude Code:

```bash
claude mcp add storekit-verify -- npx -y github:tranzen21/storekit-verify-mcp
```

Or any MCP client:

```json
{
  "mcpServers": {
    "storekit-verify": {
      "command": "npx",
      "args": ["-y", "github:tranzen21/storekit-verify-mcp"]
    }
  }
}
```

### Tools

| Tool | What it does |
|---|---|
| `verify_notification` | Full cryptographic verification → `{valid, errors[], decoded}` |
| `decode_notification` | Decode without verification (inspect payloads fast) |
| `decode_transaction` | Decode a single signed transaction/renewal JWS |
| `explain_notification_type` | What a `notificationType`/`subtype` means and how to handle it |

Ask your agent things like *"verify this webhook body and tell me if I should revoke entitlement"* — it can decode, verify, and explain in one pass.

## CLI

```bash
# Verify a webhook body (file or stdin). Exit code 0 = valid.
npx github:tranzen21/storekit-verify-mcp storekit-verify verify webhook.json \
  --bundle-id com.example.app --environment Production

# Decode without verification
storekit-verify decode webhook.json

# What does DID_FAIL_TO_RENEW / GRACE_PERIOD mean?
storekit-verify explain DID_FAIL_TO_RENEW GRACE_PERIOD
```

## Library

```ts
import { verifyNotification } from "storekit-verify-mcp";

const result = await verifyNotification(requestBody, {
  expectedBundleId: "com.example.app",
  expectedEnvironment: "Production",
});
if (!result.valid) throw new Error(result.errors.join("; "));
// result.decoded.payload.notificationType, result.decoded.transactionInfo, ...
```

## Development

```bash
npm install
npm run fixtures   # generates a test CA chain (openssl) + signed sample payloads
npm test           # builds + runs node:test suite
```

The test suite covers: valid chain verification, root-pin rejection, mismatched-key signatures, tampered payloads, bundle/environment mismatches, expired certificates, and malformed input.

## Notes

- Requires Node 20+.
- No network calls at runtime: the Apple root is bundled; verification is fully offline.
- Not affiliated with or endorsed by Apple. For the official server library, see [apple/app-store-server-library-node](https://github.com/apple/app-store-server-library-node) — this project exists to make the same verification available to MCP agents and as a zero-setup CLI.

## License

MIT © Marcquin Taylor