# Authentication > Authenticate Hookline API requests with bearer API keys, and learn the difference between test and live keys. Source: https://manueldezman.github.io/hookline-docs/docs/authentication Every request to the Hookline API needs an API key in the `Authorization` header. ```text Authorization: Bearer hl_live_replace_me ``` ## Key types | Prefix | Mode | Behavior | | --- | --- | --- | | `hl_test_` | Test | Delivers events to the CLI listener only | | `hl_live_` | Live | Delivers events to real endpoints | ## Create and rotate keys 1. Open the dashboard and select **Settings**, then **API keys**. 2. Select **Create key** and choose a mode. 3. Copy the key. Hookline shows it once. To rotate a key, create a new one, deploy it, and then revoke the old key. Both keys work until you revoke the old one. **Warning:** Never commit API keys to source control or expose them in browser code. Store them in environment variables or a secrets manager. ## Error responses A missing or invalid key returns a `401` response: ```json { "error": { "code": "invalid_api_key", "message": "The API key is missing or invalid." } } ``` A valid key without access to the resource returns `403`. --- # Introduction > Hookline delivers webhooks for you, with retries, signing, and a delivery log, so your app never loses an event. Source: https://manueldezman.github.io/hookline-docs/docs/intro Hookline is a webhook delivery service. Your app sends one event to the Hookline API. Hookline delivers it to every subscribed endpoint, retries failures, and records each attempt. ## What Hookline does - Delivers events to subscriber URLs over HTTPS. - Retries failed deliveries with exponential backoff for up to 24 hours. - Signs every request with HMAC so receivers can verify it. - Keeps a searchable delivery log for 30 days. ## How it works 1. Your app sends an event to `POST /v1/events`. 2. Hookline queues the event and returns a `202` response. 3. Hookline sends the event to each subscribed endpoint. 4. The receiver acknowledges with a `2xx` status code within 5 seconds. **Note:** Test keys deliver events to the CLI listener only. Use a live key to reach real endpoints. ## Next steps - Follow the [quick start](https://manueldezman.github.io/hookline-docs/docs/quick-start.md) to send your first event. - Learn how [authentication](https://manueldezman.github.io/hookline-docs/docs/authentication.md) works. - Read the [events reference](https://manueldezman.github.io/hookline-docs/docs/sending-events.md). - Protect receivers with [signature verification](https://manueldezman.github.io/hookline-docs/docs/verifying-signatures.md). --- # Quick start > Install the Hookline SDK, send your first event, and receive it locally with the CLI listener in about five minutes. Source: https://manueldezman.github.io/hookline-docs/docs/quick-start This guide sends one event and receives it on your machine. ## Prerequisites - Node.js 20 or later - A Hookline account and a test API key ## Install the SDK ### npm ```bash npm install @hookline/sdk ``` ### pnpm ```bash pnpm add @hookline/sdk ``` ### yarn ```bash yarn add @hookline/sdk ``` ## Send your first event Create a file named `send.mjs`: ```js import { Hookline } from '@hookline/sdk'; const hookline = new Hookline(process.env.HOOKLINE_API_KEY); const event = await hookline.events.create({ type: 'invoice.paid', payload: { invoiceId: 'inv_1042', amount: 4900 }, }); console.log(event.id, event.status); ``` Run it with your test key: ```bash HOOKLINE_API_KEY=hl_test_replace_me node send.mjs ``` The output is an event ID and the status `queued`. ## Receive the event locally Start the CLI listener in a second terminal: ```bash npx hookline listen --forward-to http://localhost:3000/webhooks ``` The listener prints each event and forwards it to your local server. **Tip:** Keep the listener running while you build. Each retry appears in the same terminal. ## Next steps Read [authentication](https://manueldezman.github.io/hookline-docs/docs/authentication.md) before you move to live keys. --- # Send events > Send events with the Hookline API, including request fields, idempotency keys, and the retry schedule for failed deliveries. Source: https://manueldezman.github.io/hookline-docs/docs/sending-events Create an event with `POST /v1/events`. Hookline queues the event and delivers it to every endpoint subscribed to its type. ## Request fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | Yes | Event name, such as `invoice.paid` | | `payload` | object | Yes | JSON body that receivers get | | `idempotency_key` | string | No | Prevents duplicate events on retries | ## Example request ### curl ```bash curl -X POST https://api.hookline.example/v1/events \ -H "Authorization: Bearer $HOOKLINE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"type":"invoice.paid","payload":{"invoiceId":"inv_1042"}}' ``` ### Node.js ```js const event = await hookline.events.create({ type: 'invoice.paid', payload: { invoiceId: 'inv_1042' }, }); ``` ### Python ```python event = hookline.events.create( type="invoice.paid", payload={"invoiceId": "inv_1042"}, ) ``` ## Response A successful request returns `202 Accepted`: ```json { "id": "evt_8f2a91", "status": "queued" } ``` ## Idempotency Send the same `idempotency_key` twice and Hookline creates one event. Keys expire after 24 hours. ## Retry schedule If an endpoint doesn't return a `2xx` status code, Hookline retries the delivery. | Attempt | Delay after previous attempt | | --- | --- | | 1 | Immediate | | 2 | 1 minute | | 3 | 10 minutes | | 4 | 1 hour | | 5 | 6 hours | After the last attempt, Hookline marks the delivery as `failed`. You can replay failed events from the dashboard. --- # Verify signatures > Verify the HMAC signature on every Hookline webhook so your receiver only accepts events that Hookline sent. Source: https://manueldezman.github.io/hookline-docs/docs/verifying-signatures Hookline signs every delivery. Verify the signature before you trust the request body. ## The signature header Each request includes a `Hookline-Signature` header: ```text Hookline-Signature: t=1700000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd ``` - `t` is the Unix timestamp when Hookline signed the request. - `v1` is the hex-encoded HMAC-SHA256 signature. ## How the signature is computed 1. Build the signed string: the timestamp, a period, and the raw request body. 2. Compute an HMAC-SHA256 of that string with your endpoint secret. Secrets start with `whsec_`. 3. Compare the result to `v1` with a constant-time comparison. **Warning:** Use the raw request body. If your framework parses the JSON first, the re-serialized body can differ and the check fails. ## Verification code ### Node.js ```js import crypto from 'node:crypto'; export function verifySignature(rawBody, header, secret) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const expected = crypto .createHmac('sha256', secret) .update(`${parts.t}.${rawBody}`) .digest('hex'); const a = Buffer.from(expected); const b = Buffer.from(parts.v1 ?? ''); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; return fresh && a.length === b.length && crypto.timingSafeEqual(a, b); } ``` ### Python ```python import hashlib import hmac import time def verify_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) signed = f"{parts['t']}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() fresh = abs(time.time() - int(parts["t"])) < 300 return fresh and hmac.compare_digest(expected, parts.get("v1", "")) ``` ## Reject old requests Both examples reject requests with a timestamp more than five minutes old. This stops replay attacks, where someone resends a captured request.