# Getting started

> From sign-up to verifying the signature of a Webhook Admin webhook on the receiving side.

Source: https://webhookadmin.com/en/docs/

Six steps from sign-up to verifying signatures on the receiving side.

## 1. Sign up

Sign up at [https://app.webhookadmin.com/signup](https://app.webhookadmin.com/signup) with your email address, Google or GitHub. With email, we send you a sign-in link that is valid for 15 minutes.

On your first sign-in, create an organization (your company or team). Plans, billing and members belong to the organization. You can own up to 2 Free organizations.

## 2. Project

Create one project per sending service. Each project has two environments: production and test.

| Environment | API key | Messages |
| --- | --- | --- |
| Production | `sk_live_…` | Count toward your monthly limit and billing (messages with no endpoint to send to are not counted) |
| Test | `sk_test_…` | Not counted (up to 1,000 a day) |

## 3. API key

Create keys per environment under “API keys” in the dashboard. The key is shown once, when you create it. Choose from five scopes.

| Scope | Allows |
| --- | --- |
| `messages:send` | Sending messages |
| `messages:retry` | Retrying deliveries |
| `logs:read` | Reading logs, consumers and endpoints |
| `endpoints:write` | Creating, updating and deleting endpoints; rotating signing secrets |
| `consumers:write` | Creating and deleting consumers |

Keys expire after 30 days, 90 days, 1 year or never. Owners and admins get an email 7 days before a key expires. A revoked key stops working within 30 seconds.

The samples on this page read the key from the `WEBHOOK_ADMIN_API_KEY` environment variable.

```bash
export WEBHOOK_ADMIN_API_KEY=sk_test_…
```

## 4. Consumer and endpoint

A consumer is the company or user who receives your webhooks. Set `external_id` to the ID you use for that customer in your service.

**curl**

```bash
curl https://api.webhookadmin.com/v1/consumers \
  -H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "cus_1024", "name": "Acme Inc." }'
```

**Node.js**

```js
const res = await fetch('https://api.webhookadmin.com/v1/consumers', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ external_id: 'cus_1024', name: 'Acme Inc.' }),
});
const consumer = await res.json(); // { id: 'con_…', external_id: 'cus_1024', … }
```

Register the URL that receives webhooks as an endpoint of that consumer. The `secret` in the response (the signing secret, starting with `whsec_`) is returned only here. The receiver uses it to verify signatures.

**curl**

```bash
curl https://api.webhookadmin.com/v1/endpoints \
  -H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "consumer_id": "con_…",
    "url": "https://example.com/webhooks",
    "event_types": ["invoice.paid"]
  }'
```

**Node.js**

```js
const res = await fetch('https://api.webhookadmin.com/v1/endpoints', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    consumer_id: consumer.id,
    url: 'https://example.com/webhooks',
    event_types: ['invoice.paid'],
  }),
});
const endpoint = await res.json(); // { id: 'ep_…', secret: 'whsec_…', … }
```

- URLs must use https on port 443 or 8443. URLs with a raw IP address and names that resolve to private addresses are rejected.
- Leave out `event_types` to receive every event.
- Set `fixed_ip: true` to deliver from the static source IP `209.71.107.233`. Ask the receiver to allow this IP.

## 5. Send

Send the consumer’s `external_id`, the event type and the payload to `POST /v1/messages`. Every endpoint of that consumer that accepts the event receives it.

**curl**

```bash
curl https://api.webhookadmin.com/v1/messages \
  -H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: inv_88-paid" \
  -d '{
    "consumer": "cus_1024",
    "event_type": "invoice.paid",
    "payload": { "invoice_id": "inv_88", "amount": 128000 }
  }'
```

**Node.js**

```js
const res = await fetch('https://api.webhookadmin.com/v1/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'inv_88-paid',
  },
  body: JSON.stringify({
    consumer: 'cus_1024',
    event_type: 'invoice.paid',
    payload: { invoice_id: 'inv_88', amount: 128000 },
  }),
});
const message = await res.json(); // { id: 'msg_…', deliveries: 1 }
```

**Response**

```
HTTP/1.1 202 Accepted

{ "id": "msg_…", "deliveries": 1 }
```

- Requests with the same `Idempotency-Key` return the first `id` for 24 hours and create no second message.
- If no consumer has that `external_id`, one is created on the spot (with no endpoints, so `deliveries` is 0).
- Check delivery under “Logs” in the dashboard or with `GET /v1/messages/{id}`.

## 6. Verify the signature

Every delivery is signed in the Standard Webhooks format, so receivers can verify it with the official libraries. Headers and the signing scheme are described in [Signature verification](https://webhookadmin.com/en/docs/sdk/).

```bash
npm install standardwebhooks express
```

**Node.js (Express)**

```js
import express from 'express';
import { Webhook } from 'standardwebhooks';

const wh = new Webhook(process.env.WEBHOOK_SECRET); // whsec_…
const app = express();

// Verify against the raw body, before any JSON parsing
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = wh.verify(req.body, req.headers);
    console.log(event.type, event.data);
    res.sendStatus(200);
  } catch {
    res.sendStatus(400);
  }
});

app.listen(3000);
```

Set `WEBHOOK_SECRET` to the `whsec_…` you received in step 4.
