# E-commerce Order Notifications

> E-commerce notifications for new orders, refund requests, low inventory, and failed payments, routed to the right team via webhooks.

Source: https://docs.alphorn.dev/use-cases/ecommerce-orders/

E-commerce notifications have different audiences - operations needs to know about new orders, finance cares about refunds, and warehouse cares about inventory. Alphorn routes each event to the right channel.

## New order notification

`fetch` only rejects on network errors, so each example checks `res.ok` and throws on a 4xx/5xx response. Catch the error where you call these functions, so a failed notification doesn't break checkout.

```javascript
async function onNewOrder(order) {
  const res = await fetch("https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.ALPHORN_API_KEY}`,
    },
    body: JSON.stringify({
      title: `New order #${order.id}`,
      message: `${order.customerName} - ${order.items.length} items - ${order.currency} ${order.total.toFixed(2)}`,
      priority: order.total > 500 ? 4 : 2,
      tags: ["order", "new", order.total > 500 ? "high-value" : "standard"],
    }),
  });
  if (!res.ok) throw new Error(`Alphorn responded ${res.status}`);
}
```

## Refund requests

```javascript
async function onRefundRequested(order, reason) {
  const res = await fetch("https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.ALPHORN_API_KEY}`,
    },
    body: JSON.stringify({
      title: `Refund requested: Order #${order.id}`,
      message: `Amount: ${order.currency} ${order.total.toFixed(2)}\nReason: ${reason}\nCustomer: ${order.customerEmail}`,
      priority: 3,
      tags: ["order", "refund"],
    }),
  });
  if (!res.ok) throw new Error(`Alphorn responded ${res.status}`);
}
```

## Low inventory alerts

```python
import os
import requests

def check_inventory(products, threshold=5):
    for product in products:
        if product.stock <= threshold:
            requests.post(
                "https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT",
                headers={"Authorization": "Bearer " + os.environ["ALPHORN_API_KEY"]},
                json={
                    "title": f"Low stock: {product.name}",
                    "message": f"Only {product.stock} units remaining (SKU: {product.sku})",
                    "priority": 4 if product.stock == 0 else 3,
                    "tags": ["inventory", "low-stock",
                             "out-of-stock" if product.stock == 0 else "warning"],
                },
                timeout=10,
            ).raise_for_status()  # raises on 4xx/5xx
```

## Failed payment alerts

```javascript
async function onPaymentFailed(order, error) {
  const res = await fetch("https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.ALPHORN_API_KEY}`,
    },
    body: JSON.stringify({
      title: `Payment failed: Order #${order.id}`,
      message: `${error.message}\nCustomer: ${order.customerEmail}\nAmount: ${order.currency} ${order.total.toFixed(2)}`,
      priority: 3,
      tags: ["order", "payment-failed"],
    }),
  });
  if (!res.ok) throw new Error(`Alphorn responded ${res.status}`);
}
```

## Routing examples

| Channel | Filter | Purpose |
|---|---|---|
| Slack (#orders) | Tags has any of `new` | New order feed |
| Slack (#finance) | Tags has any of `refund` | Refund requests for finance team |
| Slack (#warehouse) | Tags has any of `inventory` | Stock alerts for warehouse |
| Telegram | Tags has any of `high-value` | Founder gets notified on big orders |
| Email | Tags has any of `order` | Full order archive |
| PagerDuty | Tags has any of `out-of-stock` | Escalate sold-out products |
