# Quickstart

This is the shortest trustworthy Hooksbase flow: create a project, provision one webhook, publish one event into it, inspect the resulting delivery, and verify the outbound signature in your app. Plan on about five minutes end to end.

## Prerequisites

Before choosing a path you need:

1. A Hooksbase account. Dashboard users can create the project during the dashboard path; API, SDK, and CLI users need an existing project.
2. A destination URL your app can receive requests on. For local testing, use a tunnel such as `ngrok`, `cloudflared tunnel`, or any public HTTPS endpoint you control.
3. For API, SDK, or CLI setup, a project API key (prefix `swk_`). Create it from the dashboard under project settings. `write` keys can create and operate webhooks, deliveries, replay, DLQ, schedules, and usage; `admin` keys are required for `/v1/project`, project API-key management, audit logs, and operator notification routes.

> [!NOTE]
> Hooksbase returns raw ingest and signing secrets **only** on the create-webhook and rotate-secret responses. Copy them out of the response before the shell window closes — list and get routes never return them again.

## Auth model

Three different credentials appear in this flow. Each one authenticates a different surface, and they are not interchangeable:

- **Project API key** `swk_...`: Project-scoped credential for the Public API (`Authorization: Bearer swk_...`). Admin role can create webhooks; write role can replay, list deliveries, and call `/v1/usage`.
- **Ingest secret** `whsec_...`: Per-webhook credential used when sending events to the public ingest URL (`Authorization: Bearer whsec_...`). Not valid for any other Hooksbase API route.
- **Signing secret** `sws_...`: Outbound-only. Hooksbase signs delivery requests to your destination with this secret; your app verifies the signature before trusting the payload. Never sent back to Hooksbase.

## Choose your path

**Choose your path**

Start with the path that matches the person or system doing the work. All paths create the same kind of webhook and delivery history.

| Surface | Status | Notes |
| --- | --- | --- |
| [Dashboard](#dashboard-path) | Preferred | Best for first setup, non-technical operators, project creation, onboarding, and visual delivery inspection. |
| [Public API](#api-and-sdk-path) | Available | Best for integrations, repeatable provisioning, CI, and backend workflows. |
| [TypeScript SDK](#api-and-sdk-path) | Available | Best for Node.js and TypeScript apps that need typed helpers for create, ingest, and signature verification. |
| [CLI](#cli-and-agent-path) | Available | Best for agents, scripts, terminal operators, and JSON-first automation. |

## Dashboard path

Use this path when a product operator, founder, support engineer, or non-code teammate needs to get the first workflow running from the UI.

1. Open [app.hooksbase.com](https://app.hooksbase.com) and create or select a project.
2. From the project home, start onboarding and choose the blueprint closest to your source: inbound HTTP, provider webhook, email, form, or scheduled.
3. Enter the destination URL where your app should receive deliveries. For a local app, use a public tunnel URL.
4. Create the webhook and copy the one-time ingest and signing secrets into your password manager or secret manager.
5. Run onboarding validation. This sends a sample payload through the saved destination and marks the first workflow complete when it succeeds.
6. Open the webhook's deliveries view to inspect the attempt timeline, body preview, response status, and replay action.
7. Invite teammates from project settings and open billing only when the workflow needs a paid tier feature or higher limits.

Use this path for setup and operational review. When a workflow needs to be repeated by CI, backend code, or an agent, move to the API, SDK, or CLI paths below.

## API and SDK path

Use this path when you are building an integration or provisioning webhooks from code. The examples use the public route host for management APIs and the public ingest host for event publishing.

### 1. Create a webhook

Use a project API key to create a webhook with one default destination. The create response includes the public ingest URL, the form ingest URL, the email ingest address, the one-time ingest secret, and the one-time signing secret. Replace `https://example.com/hooksbase` with your own destination.

**Create a webhook with cURL**

```bash
curl https://api.hooksbase.com/v1/webhooks \
  -H "Authorization: Bearer swk_..." \
  -H "content-type: application/json" \
  -d '{
    "defaultDestination": {
      "name": "default",
      "type": "webhook",
      "config": {
        "url": "https://example.com/hooksbase",
        "customHeaders": {
          "x-tenant-id": "tenant_123"
        }
      }
    },
    "deliveryMode": "parallel",
    "timeoutMs": 15000
  }'
```

**Create a webhook with the SDK**

```ts
import { createHooksbaseClient } from '@hooksbase/sdk'

const client = createHooksbaseClient({
  apiKey: process.env.HOOKSBASE_API_KEY,
})

const created = await client.webhooks.create({
  defaultDestination: {
    name: 'default',
    type: 'webhook',
    config: {
      url: 'https://example.com/hooksbase',
      customHeaders: {
        'x-tenant-id': 'tenant_123',
      },
    },
  },
  deliveryMode: 'parallel',
  timeoutMs: 15000,
})

console.log(created.id, created.ingestUrl, created.ingestSecret, created.signingSecret)
```

The webhook create response is the only time Hooksbase returns the raw `ingestSecret` and `signingSecret`. Save them immediately — you can rotate them later, but you cannot retrieve the existing values.

**Create response fields to save**

```json
{
  "id": "wh_123",
  "publicId": "hook_123",
  "ingestUrl": "https://hooks.hooksbase.com/v1/ingest/hook_123",
  "formIngestUrl": "https://hooks.hooksbase.com/v1/form/hook_123",
  "emailIngestAddress": "hook_123@ingest.hooksbase.com",
  "ingestSecret": "whsec_...",
  "signingSecret": "sws_..."
}
```

### 2. Send your first event

Publish into the webhook's `ingestUrl` with the matching `whsec_...` secret. Public ingest lives on the `hooks.hooksbase.com` host (not `api.hooksbase.com`). Add an `Idempotency-Key` so retries do not create duplicate deliveries.

**Publish one event with cURL**

```bash
curl "${INGEST_URL}" \
  -H "Authorization: Bearer ${INGEST_SECRET}" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: quickstart-order-1" \
  -d '{
    "type": "order.created",
    "orderId": "ord_123",
    "amount": 4200
  }'
```

**Publish with the SDK ingest helper**

```ts
await client.ingest.publishJson({
  ingestUrl: created.ingestUrl!,
  ingestSecret: created.ingestSecret!,
  idempotencyKey: 'quickstart-order-1',
  payload: {
    type: 'order.created',
    orderId: 'ord_123',
    amount: 4200,
  },
})
```

A successful response includes `deliveryId`, `sequenceNo`, and a `status` of `queued` or `scheduled`. If you are on Pro or higher, you can delay the delivery by sending `x-hooksbase-deliver-at` (milliseconds since epoch) or the SDK's `deliverAt` option.

**Ingest success response**

```json
{
  "deliveryId": "del_123",
  "sequenceNo": 1,
  "status": "queued",
  "duplicate": false
}
```

### 3. Inspect the delivery

The ingest response tells you whether the delivery was queued immediately or scheduled for the future. To inspect the resulting delivery and its attempts, list project deliveries or fetch the delivery by ID.

**List the newest delivery for one webhook**

```bash
curl -G https://api.hooksbase.com/v1/deliveries \
  -H "Authorization: Bearer swk_..." \
  -d webhookId=wh_... \
  -d limit=1
```

**Fetch a delivery with its attempt history**

```bash
curl https://api.hooksbase.com/v1/deliveries/del_... \
  -H "Authorization: Bearer swk_..."
```

Use the dashboard when you want body previews, attempt history, replay buttons, or the broader project context around that delivery.

## CLI and agent path

Use this path when the caller is a terminal operator, script, coding agent, or LLM workflow. The CLI should read remote state, perform one explicit action, re-read the target, and stop.

**Install and configure the CLI**

```bash
npm install --global @hooksbase/cli

export HOOKSBASE_BASE_URL=https://api.hooksbase.com
export HOOKSBASE_API_KEY=swk_...

hooksbase auth login --api-key "$HOOKSBASE_API_KEY" --base-url "$HOOKSBASE_BASE_URL"
hooksbase auth profiles --validate --json
```

Create the webhook request file once, then let the CLI send it:

**webhook.json**

```json
{
  "name": "orders",
  "defaultDestination": {
    "name": "default",
    "type": "webhook",
    "config": {
      "url": "https://example.com/hooksbase",
      "customHeaders": {
        "x-tenant-id": "tenant_123"
      }
    }
  },
  "deliveryMode": "parallel",
  "timeoutMs": 15000
}
```

**Create, inspect, send, and re-read**

```bash
hooksbase webhooks create --file ./webhook.json --json
hooksbase webhooks list --limit 20 --json

hooksbase ingest send \
  --webhook-name orders \
  --ingest-secret whsec_... \
  --file ./payload.json \
  --idempotency-key quickstart-order-1 \
  --json

hooksbase deliveries list --webhook-id wh_123 --limit 1 --json
```

Agent-safe defaults:

- always request `--json`
- page with `--limit` and `--cursor`
- use `--webhook-name` when the agent has a project API key and should resolve the current ingest URL
- send an idempotency key when a command may be retried
- re-read the exact resource you changed before taking another action
- do not automate dashboard-only workflows from a browser session

## Verify the signature

When Hooksbase delivers the event to your destination, it includes `webhook-id`, `webhook-timestamp`, and `webhook-signature`. Verify the signature against the raw request body before trusting the payload.

**Verify with the SDK helper**

```ts
import { verifyHooksbaseWebhook } from '@hooksbase/sdk'

const rawBody = await request.arrayBuffer()

const { deliveryId, timestamp } = verifyHooksbaseWebhook({
  headers: request.headers,
  rawBody,
  signingSecret: process.env.HOOKSBASE_SIGNING_SECRET!,
})
```

The helper throws if the signature, timestamp window, or headers are invalid. Always verify against the **raw body bytes**, not a re-serialized parsed JSON object — even semantically identical JSON can produce different bytes.

During a signing-secret overlap window, Hooksbase may send signatures for both the current and previous secret version. The helper accepts a single secret or an array of secrets:

**Accept multiple signing secrets during rotation**

```ts
verifyHooksbaseWebhook({
  headers: request.headers,
  rawBody,
  signingSecret: [
    process.env.HOOKSBASE_CURRENT_SIGNING_SECRET!,
    process.env.HOOKSBASE_PREVIOUS_SIGNING_SECRET!,
  ],
})
```

## Common mistakes

- Losing the one-time secrets returned from webhook creation or secret rotation.
- Using the signing secret for ingest, or the ingest secret for signature verification.
- Omitting `Idempotency-Key` on retryable ingest requests.
- Assuming replay re-evaluates the webhook's current transform or routing settings. Replay reuses the persisted delivery snapshot.

Next steps:

- Read the [dashboard overview](/docs/dashboard.md) if you want the product tour.
- Read [webhooks](/docs/webhooks.md) for lifecycle controls and tier-gated features.
- Read [ingest](/docs/ingest.md) for public ingest behavior and scheduling.
- Read [deliveries](/docs/deliveries.md) for replay, replay jobs, and DLQ workflows.
- Read [webhook security](/docs/webhook-security.md) for secret rotation and deeper signing details.
