---
title: Quickstart
description: From a signup form on your site to a confirmed subscriber, in about five minutes.
---

This walks the whole loop: a form on your site, a confirmation email, a
confirmed subscriber you can send to.

## 1. Create a topic

In the dashboard, under **Audience → Topics**, create one. Call it
something a recipient would recognise, because they will see the name
on the unsubscribe page: "Weekly newsletter" rather than "list-2".

Note its id. It looks like `tp_7xk2m9qv4w`.

## 2. Get a publishable key

**Settings → API keys**. Create a key and pick **publishable**.

Publishable keys are safe in front-end code. They can do exactly one
thing — subscribe somebody to a topic — and nothing else. Secret keys,
which can read and change your audience, must never leave your server.

Start with a **test** key. Test keys validate everything and send
nothing, so you can get your form working without mailing anyone. More
on that in [test and live keys](/guide/test-and-live).

## 3. Post a signup

```bash
curl https://api.zevcampaign.com/v1/subscribe \
  -H "Authorization: Bearer pk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "topic_id": "tp_7xk2m9qv4w",
    "email": "ada@example.com",
    "first_name": "Ada"
  }'
```

```json
{
  "data": {
    "status": "confirmation_sent"
  }
}
```

From a browser, the same call with `fetch`:

```js
await fetch('https://api.zevcampaign.com/v1/subscribe', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer pk_live_your_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    topic_id: 'tp_7xk2m9qv4w',
    email: form.email.value,
    first_name: form.firstName.value,
  }),
});
```

## 4. Expect one of two answers

`status` is either `confirmation_sent` or `subscribed`, and that is
all you get.

It is deliberately this coarse. The response is identical whether the
address was new, already subscribed, still pending, or previously
unsubscribed. Anything more specific would turn your public form into a
way for anyone to check whether a particular person is on your list,
which is their business and not something we will leak on your behalf.

Show the same friendly message either way: *"Check your inbox to
confirm."*

## 5. They confirm

On a double opt-in topic, which is the default, we email the address a
confirmation link **from your brand**, not from us. Someone who typed
their address into your bakery's newsletter box expects to hear from
the bakery.

When they click it, the subscription becomes active and your
`contact.subscribed` webhook fires. That event always means real
consent, which is why it is worth wiring up: your own CRM can trust it
without re-checking.

## 6. Send

Build the campaign in the dashboard and send it to the topic.

If you are on a trial, or your brand has not been reviewed yet, you can
only send to addresses you have verified as your own. That is not a bug
and it is not a quota: see [getting approved](/guide/approval) for what
lifts it and how long it takes.

## What next

- [Consent and double opt-in](/guide/consent) — what we record, and why
- [Webhooks](/webhooks/) — so your systems hear about this too
- [Contacts API](/api/contacts) — importing an audience you already have