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

# Quickstart

> Register an endpoint and verify your first event in about five minutes.

By the end of this guide you'll have a running endpoint that receives a real event from Ionic and verifies its signature. Everything here is in test mode — use a `sk_v1_test_…` key.

## 1. Stand up a receiver

A webhook endpoint is an HTTPS URL that accepts a `POST`. Start with a handler
that captures the **raw** body (the signature covers the exact bytes, so a body
that's been parsed and re-serialized by framework middleware will not verify)
and acknowledges quickly.

On Node, `webhooks.unwrap()` from the server SDK checks the signature and
replay window, then returns a typed event:

```bash theme={null}
npm install @ionicfi/sdk
```

```ts Express theme={null}
import express from "express";
import { WebhookParseError, WebhookVerificationError, webhooks } from "@ionicfi/sdk";

const app = express();

app.post("/webhooks/ionic", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = webhooks.unwrap(req.body, req.headers, process.env.IONIC_WEBHOOK_SECRET);
  } catch (err) {
    if (err instanceof WebhookVerificationError || err instanceof WebhookParseError) {
      return res.sendStatus(400); // reject anything we can't verify
    }
    throw err;
  }

  console.log("received", event.type, event.id);

  res.sendStatus(200); // acknowledge fast; do real work asynchronously
});

app.listen(4242);
```

Not on Node? The signature scheme is standard HMAC-SHA256 over
`{id}.{timestamp}.{body}` — [Verify signatures](/webhooks/signatures) has the
full manual implementation in Node, Python, and Go.

<Tip>
  Developing locally? Endpoints must be HTTPS, so `localhost` alone won't work. Put an HTTPS tunnel in front of your server and register the tunnel's public URL in the next step.
</Tip>

## 2. Register the endpoint

Tell Ionic where to deliver and which events you want. For this walkthrough, subscribe to `payment_intent.created`.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Ionic } from "@ionicfi/sdk";

  const ionic = new Ionic({ token: process.env.IONIC_SECRET_KEY });

  const endpoint = await ionic.webhookEndpoints.create({
    "Idempotency-Key": "quickstart-webhook-endpoint",
    url: "https://your-tunnel.example.com/webhooks/ionic",
    event_types: ["payment_intent.created"],
    description: "Quickstart",
  });

  console.log(endpoint.secret); // whsec_… — shown only in this response
  ```

  ```bash curl theme={null}
  curl https://api.ionicfi.com/v1/webhook_endpoints \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: quickstart-webhook-endpoint" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://your-tunnel.example.com/webhooks/ionic",
      "event_types": ["payment_intent.created"],
      "description": "Quickstart"
    }'
  ```
</CodeGroup>

The response includes a `secret` that starts with `whsec_`. **This is the only time it's shown.** Save it where your server can read it:

```bash theme={null}
export IONIC_WEBHOOK_SECRET="whsec_..."   # from the create response
```

## 3. Verify every delivery

Your handler already does: `unwrap` recomputes the signature over the raw body
with constant-time comparison, rejects deliveries older than the replay
window, and throws on anything that doesn't match. Don't skip verification:
an unverified endpoint will accept forged events from anyone who learns your
URL. If you're implementing it yourself, [Verify
signatures](/webhooks/signatures) walks through the same checks step by step.

## 4. Trigger an event

Create a payment intent. This needs only an amount and currency — no card — and emits `payment_intent.created`.

<CodeGroup>
  ```ts TypeScript theme={null}
  await ionic.paymentIntents.create({
    amount: 1000,
    currency: "usd",
  });
  ```

  ```bash curl theme={null}
  curl https://api.ionicfi.com/v1/payment_intents \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Content-Type: application/json" \
    -d '{ "amount": 1000, "currency": "usd" }'
  ```
</CodeGroup>

Within a moment your endpoint receives the event and logs:

```
received payment_intent.created evt_2pXqL9mZ4kRb7VnT0sWcEfGh
```

## 5. You're receiving webhooks

That's the whole loop: register, verify, receive. Because `unwrap` returns a
typed event, handling a specific type narrows the payload for you:

```ts theme={null}
if (event.type === "checkout.session.completed") {
  await fulfill(event.data.object); // typed CheckoutSession
}
```

From here:

<CardGroup cols={2}>
  <Card title="Event catalog" icon="list" href="/webhooks/events">
    Subscribe to the events you actually handle — `payment_intent.succeeded`, `refund.succeeded`, and the rest.
  </Card>

  <Card title="Idempotency & retries" icon="arrows-rotate" href="/webhooks/idempotency-and-retries">
    Make your handler safe against duplicate and out-of-order deliveries before you ship.
  </Card>
</CardGroup>

## Developing locally

Ionic can't deliver webhooks to `localhost`. Two ways to work while local:

* **Read the current status.** Fetch the resource directly when you need an
  answer — for example `GET /v1/checkout/sessions/{id}` returns
  `payment_status` on demand. This works for most local testing.
* **Tunnel your receiver.** Expose your local port (for example
  `ngrok http 3000`), register the tunnel URL as a webhook endpoint, and
  update the endpoint's `url` whenever the tunnel address changes.

In production, fulfill from a verified webhook so paid orders do not depend on
the buyer returning to your site.
