> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message` from the Node SDK (`@trycourier/courier` v7 and later, where the client is the default import). Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Inbound webhooks

> Point an external system's webhooks at a Courier URL to update profiles and start journeys.

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

Courier gives you a URL. You point an external system's webhooks at it.

Courier parses each arriving event and makes it available to the platform, most often as a <Doc href="/docs/journeys/invoke">webhook trigger</Doc> on a journey. Use one when a third-party system can send an HTTP request but cannot call the Courier API directly.

## How it works

### Register a webhook

1. In <AppLink href="https://app.courier.com/settings/webhooks">Settings > Webhooks</AppLink>, under **Developers**, find the **Inbound Webhooks** section.
2. Select **Add**, then give the webhook a name and description.
3. Save. Courier generates a unique URL in the form `https://api.courier.com/inbound/webhook/<token>`.

The name is permanent. You select the webhook by name wherever you consume its events, so make it recognizable.

### Send events

Point the source system at that URL and `POST` your JSON. No API key or auth header is needed. The URL carries a signed token that identifies your workspace.

<Warning>
  Treat the webhook URL as a secret. Anyone who has it can send events into your workspace. If it leaks, delete the webhook and create a new one for a fresh URL.
</Warning>

```bash theme={null}
curl -X POST https://api.courier.com/inbound/webhook/YOUR_WEBHOOK_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "event": "order-shipped",
    "userId": "user_123",
    "properties": {
      "order_id": "ORD-9042",
      "carrier": "UPS"
    }
  }'
```

Send real traffic before you build against the webhook. Courier learns event names and payload shape from events it has received. Event pickers and variable hints stay empty until the first one arrives.

### Payload handling

Courier accepts any payload up to 6 MB.

| Payload       | What Courier does                                                            |
| ------------- | ---------------------------------------------------------------------------- |
| JSON object   | Parses it and exposes the fields as data                                     |
| JSON array    | Unpacks it and treats each object as its own event                           |
| Anything else | Keeps the payload as a string on a `raw` field, and names the event `custom` |

Courier answers `202` when it accepts the request. A `404` means the URL is wrong or no longer exists.

### Reserved fields

Courier reads two fields from a JSON payload, if present.

| Field    | Purpose                                                                                                                                                                   |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`  | The event name, used to pick which events start a workflow. Must be a string. Without it, Courier names the event `custom`.                                               |
| `userId` | Identifies the recipient. Courier resolves the <Doc href="/docs/recipients/overview">user</Doc> from it and loads their profile data. A string or number, coerced to a string. |

Every other field is yours. Courier passes them through as data to whatever consumes the event.

## Limits & behavior

* **`userId` must match an existing Courier user.** With no recipient to send to, the event will not start a journey.
* **The URL is the credential.** There is no separate auth header, so anyone holding the URL can post events.
* **Payloads cap at 6 MB.** Larger requests are rejected.

## FAQ

<AccordionGroup>
  <Accordion title="Why is my event picker empty?">
    Courier learns event names from traffic it has received. Post one real event and the name appears.
  </Accordion>

  <Accordion title="What if the webhook URL leaks?">
    Delete the webhook and create a new one. That issues a fresh URL, and the old one stops accepting events.
  </Accordion>

  <Accordion title="Can I send a batch of events at once?">
    Post a JSON array. Courier treats each object in it as its own event.
  </Accordion>
</AccordionGroup>
