# Quickstart

Create a source, a destination and a flow, then send a test event end to end.

In this quickstart you create a source, point it at a destination, and send a webhook through it. It takes a few minutes and needs only a URL that can receive a `POST`. A request inspector URL is fine.

You will:

1. Create a destination.
2. Create a source.
3. Connect them with a flow.
4. Send a test event.
5. Read the event's timeline.

## Before you begin

Sign in to the [dashboard](https://app.getensue.com). To follow along over the API instead, create an API key under **Settings → API keys & MCP**; the [API introduction](/api-reference/introduction/) covers authentication.

## 1. Create a destination

Open **Destinations → New destination**, name it, and enter the URL that should receive events, for example `https://api.example.com/webhooks/ensue`.

```bash
curl -X POST https://api.getensue.com/v1/destinations \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{"name": "billing-api", "url": "https://api.example.com/webhooks/ensue"}'
```

## 2. Create a source

Open **Sources → New source**, name it, and choose how the sender signs its requests. Use **None** for this test; switch to a real method before going live ([Verify incoming webhooks](/gateway/verify-webhooks/)).

```bash
curl -X POST https://api.getensue.com/v1/sources \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{"name": "stripe-test"}'
```

The response includes the source's ingest URL:

```text
https://hooks.getensue.com/in/<token>
```

This is the URL you paste into the sender's webhook settings.

## 3. Connect them with a flow

Open **Flows → New flow**, choose the source and the destination, and activate it. You can add a [filter](/gateway/filters/) or [transformation](/gateway/transformations/) now or later.

```bash
curl -X POST https://api.getensue.com/v1/flows \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{"source_id": "src_…", "destination_id": "dst_…"}'
```

## 4. Send a test event

Post to the source URL as a sender would:

```bash
curl -X POST https://hooks.getensue.com/in/<token> \
  -H "content-type: application/json" \
  -d '{"type": "test.ping", "data": {"hello": "world"}}'
```

Ensue answers as soon as the request is stored:

```json
{ "received": true, "request_id": "req_…", "events": 1 }
```

`events: 1` means one flow matched and one event was queued for delivery. You can also use **Send test event** on the source page, which needs no `curl`.

## 5. Read the timeline

Open **Events** and select the event. The timeline shows when it was received, whether it was verified, each delivery attempt, your service's response and how long it took.

```text
12:04:11  received · verified
12:04:11  matched flow stripe-test → billing-api
12:04:11  attempt 1 · 200 in 142ms
```

## What your service receives

Ensue forwards the request body and adds headers so you can verify and de-duplicate:

| Header | Meaning |
|---|---|
| `x-ensue-event-id` | The event ID. Stable across retries. |
| `idempotency-key` | The same value, for libraries that look for this header. |
| `x-ensue-request-id` | The request that created the event. |
| `x-ensue-timestamp` | When this attempt was sent, in Unix seconds. |
| `x-ensue-signature` | `t=<timestamp>,v1=<hex>`: the HMAC-SHA256 of `"<timestamp>.<body>"` using the destination's secret. |

> [!TIP]
> Verify `x-ensue-signature` against the raw request body, before parsing JSON, and reject timestamps that are more than a few minutes old.

## What you have learned

You created the three objects every Gateway setup is made of and watched an event travel through them. Next:

- [Verify incoming webhooks](/gateway/verify-webhooks/) so only the real sender can post to your source.
- [Delivery policy](/gateway/delivery-policy/) to control retries and rate.
- [Replay and dead letters](/gateway/replay-and-dead-letters/) for when your service is down.