# Subscribe URL

> Every audience has a hosted subscribe URL that adds people without an API key. Learn the request format, how to call it safely, its limits and how to reset it.

Each audience has a subscribe URL: a public endpoint that adds a person to the audience without an API key. Use it to connect a sign-up form on your site, or a no-code tool that can send a JSON request, to an audience.

```text
POST https://api.emailit.com/subscribe/{token}
```

The `token` is secret to the audience. Anyone who has the URL can add addresses to the audience, so treat it like a password.

## Find the URL

1. **Open the audience.** Go to **Email Marketing → Audiences** and select the audience.

2. **Copy the URL.** Select **Subscribe URL** and copy the URL from the dialog.

With the API, [Retrieve an audience](/docs/api-reference/audiences/get/) returns the same value in `token`.

## Request format

Send a `POST` request with a JSON body and `Content-Type: application/json`. No `Authorization` header is needed.

- `email` (string, required): The address to subscribe. Stored in lowercase.
- `first_name` (string): The person's first name. Overwrites the stored first name of an existing contact.
- `last_name` (string): The person's last name. Overwrites the stored last name of an existing contact.
- `custom_fields` (object): Values keyed by [custom field](/docs/contacts/custom-fields/) key. Replaces all custom field values of an existing contact, so only send it when you have the full set.

The endpoint only reads JSON. Form-encoded bodies, which a plain HTML `<form>` sends, are rejected with `400` and "Invalid JSON in request body".

### Responses

| Status | Body | When |
| --- | --- | --- |
| `200` | `{ "message": "Subscribed successfully" }` | The person was added, resubscribed or was already subscribed. |
| `400` | `{ "error": "Missing required field: email" }` or `{ "error": "Invalid email format" }` | The email is missing or malformed, or the body isn't JSON. |
| `404` | `{ "error": "Audience not found" }` | The token is wrong or was reset. |
| `422` | `{ "error": "...", "usage": { ... } }` | The audience reached its [subscriber limit](/docs/audiences/#limits). |
| `429` | | More than 30 requests in a minute from the same IP address. |

### What a sign-up does

- **New address:** Emailit creates the contact and subscribes it to the audience.
- **Existing contact, not on the audience:** Emailit updates the names and custom fields you sent and subscribes the contact.
- **Existing subscriber who had unsubscribed:** Emailit subscribes them again.
- **Existing subscriber who is subscribed:** nothing changes except the subscription date, and the response is still `200`.

Sign-ups through the subscribe URL don't send `subscriber.*` or `contact.*` [webhook events](/docs/webhooks/event-types/) and don't start **Added to audience** automations. They also don't change a contact's marketing status: if the contact was globally unsubscribed, campaigns keep skipping it until you resubscribe it on the Contacts list.

## Connect a sign-up form

The subscribe URL has no bot protection, and it only accepts JSON. The safest setup is to post your form to your own server, check it there, and call the subscribe URL from the server. That keeps the token out of your page source and lets you block spam before it reaches your audience.

1. **Add the form to your page.** Post it to an endpoint on your own site. The hidden `website` field is a honeypot: people don't see it, but bots often fill it in.

```html title="signup.html"
<form id="signup" method="post" action="/newsletter">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <label for="first_name">First name</label>
  <input id="first_name" name="first_name" type="text">

  <!-- Honeypot: hidden from people, often filled in by bots -->
  <div style="position:absolute;left:-10000px" aria-hidden="true">
    <input name="website" type="text" tabindex="-1" autocomplete="off">
  </div>

  <button type="submit">Subscribe</button>
  <p class="status" role="status"></p>
</form>
```

2. **Handle the form on your server.** Drop honeypot hits, check a CAPTCHA if you use one, then forward the fields to the subscribe URL as JSON. Keep the token in an environment variable such as `EMAILIT_SUBSCRIBE_TOKEN`. See the [server examples](#server-examples) below.

3. **Test it.** Submit the form with your own address and check that you appear in the audience's subscribers table.

### Server examples

**Node.js**

```javascript title="server.js"
import express from 'express';

const app = express();

app.post('/newsletter', express.urlencoded({ extended: false }), async (req, res) => {
  // Bots fill in the honeypot. Pretend it worked and stop.
  if (req.body.website) return res.redirect(303, '/thanks');

  const response = await fetch(
    `https://api.emailit.com/subscribe/${process.env.EMAILIT_SUBSCRIBE_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: req.body.email,
        first_name: req.body.first_name || undefined,
      }),
    },
  );

  if (!response.ok) {
    const { error } = await response.json().catch(() => ({}));
    return res.status(response.status).send(error || 'Could not subscribe.');
  }

  res.redirect(303, '/thanks');
});

app.listen(3000);
```

**PHP**

```php title="newsletter.php"
<?php
// Bots fill in the honeypot. Pretend it worked and stop.
if (!empty($_POST['website'])) {
    header('Location: /thanks', true, 303);
    exit;
}

$token = getenv('EMAILIT_SUBSCRIBE_TOKEN');
$ch = curl_init("https://api.emailit.com/subscribe/{$token}");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'email' => $_POST['email'] ?? '',
        'first_name' => $_POST['first_name'] ?? null,
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    http_response_code($status);
    echo json_decode($body, true)['error'] ?? 'Could not subscribe.';
    exit;
}

header('Location: /thanks', true, 303);
```

**cURL**

```bash
curl "https://api.emailit.com/subscribe/$EMAILIT_SUBSCRIBE_TOKEN" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "first_name": "Ada" }'
```

If your page submits with JavaScript instead of a full page load, call your own endpoint with `fetch` and keep the Emailit call on the server:

```javascript title="signup.js"
document.querySelector('#signup').addEventListener('submit', async (event) => {
  event.preventDefault();
  const form = new FormData(event.target);
  const response = await fetch('/newsletter', { method: 'POST', body: new URLSearchParams(form) });
  event.target.querySelector('.status').textContent = response.ok
    ? 'Thanks, you are subscribed.'
    : 'Something went wrong. Please try again.';
});
```

### Rate limit and higher volumes

The subscribe URL accepts 30 requests per minute from each IP address and returns `429` above that. When your server forwards every sign-up, they all come from your server's IP, so the 30 per minute apply to your whole site. If you expect more, call [Add a subscriber](/docs/api-reference/audiences/subscribers/add/) from your server with an API key instead. That endpoint also returns `409` for people who are already subscribed and starts **Added to audience** automations, for example to send a welcome email.

## Confirm sign-ups

The subscribe URL adds people immediately. Emailit doesn't send a confirmation email or ask the person to confirm their address (double opt-in).

If you want confirmed sign-ups, build the confirmation into your own flow: when someone submits the form, store the request on your server and [send them an email](/docs/email-api/send-email/) with a confirmation link that points back to your site. Call the subscribe URL only after they open that link.

## Reset the URL

Reset the token if the URL leaked or you're getting spam sign-ups. The old URL stops working immediately and returns `404`.

1. **Open the dialog.** On the audience page, select **Subscribe URL**.

2. **Reset.** Select **Reset token** and confirm. Emailit generates a new URL.

3. **Update your integrations.** Replace the token everywhere you use it, such as the `EMAILIT_SUBSCRIBE_TOKEN` variable on your server.

Resetting the token is only available in the dashboard.

## Related

  - [Manage subscribers](/docs/audiences/subscribers/): Add people with the API and handle resubscribes.
  - [Unsubscribes](/docs/audiences/unsubscribes/): Let people leave your audiences.

---
Source: https://emailit.com/docs/audiences/subscribe-url/
