Skip to main content

Connecting your website signup form to Bookboost

The single request that creates the guest profile, adds it to a list, and records consent, with the response codes and what to show the visitor for each.

For the common case, connecting a signup form on your own website to Bookboost is one HTTP request. It creates the profile, adds it to the list, and records the consent in one go. Whether the guest then receives a confirmation email or a welcome email is decided by the consent's configuration in Bookboost, not by anything you send. See Consent management essentials for that behaviour, and Add subscribers to a list via the API for the Bookboost side of the setup.

Before you start

You need three things from whoever set up the Bookboost side:

  • An API key for this integration, generated by Bookboost support and found under Settings > Access Tokens

  • The UUID of the list to add the guest to, from Profiles > Lists

  • The UUID of the consent to record, from Settings > Consent Form

Keep all three in server-side configuration, not in your code and not in the browser. Your form posts to your own backend, and your backend calls Bookboost. An API key in front-end JavaScript can be read by anyone and used to write into the hotel's guest database.

The request

POST https://cdp.bookboost.io/api/v1/users/store-in-list

Headers:

Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Accept: application/json

HTTPS only. Plain HTTP is rejected.

What to send

Field

Required

Notes

list_id

Yes

UUID of the list

consent_id

Yes

UUID of the consent

email

Yes

Valid email address, 6 to 100 characters

first_name

No

Up to 255 characters

last_name

No

Up to 255 characters

phone

No

International format, for example +31628233622

salutation

No

How to address the guest, for example Ms

title

No

Up to 16 characters, for example Dr

gender

No

Up to 7 characters

language_code

No

Exactly two letters, for example EN. Send this. It selects the language of the confirmation and welcome emails

country_code

No

Exactly two letters, for example SE

nationality

No

Exactly two letters

tags

No

Up to 100 tag names. Tags that do not exist yet are created

language_code is optional but worth treating as required. Leave it out and the guest falls back to the campaign's default language, which is hard to correct afterwards.

Use tags for anything you want to slice on later, such as which form the signup came from or which campaign drove it.

cURL

curl --request POST \
  --url https://cdp.bookboost.io/api/v1/users/store-in-list \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "list_id": "e0249634-e67f-3fb4-9a9b-d8dcbe43ced8",
    "consent_id": "7c1f0a52-1d4e-4a55-9f2b-3c8d5e6a7b90",
    "email": "maria@example.com",
    "first_name": "Maria",
    "last_name": "Lindqvist",
    "language_code": "EN",
    "country_code": "SE",
    "tags": ["website-signup", "footer-form"]
  }'

Node.js

// Runs on your server, never in the browser.
const res = await fetch(
  "https://cdp.bookboost.io/api/v1/users/store-in-list",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BOOKBOOST_API_KEY}`,
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    body: JSON.stringify({
      list_id: process.env.BOOKBOOST_NEWSLETTER_LIST_ID,
      consent_id: process.env.BOOKBOOST_MARKETING_CONSENT_ID,
      email: form.email,
      first_name: form.firstName,
      last_name: form.lastName,
      language_code: form.language ?? "EN",
    }),
  }
);if (res.status === 201) {
  // Double opt-in: "check your inbox". Single opt-in: "you are subscribed".
} else if (res.status === 409) {
  // Already subscribed. Show the same friendly message.
} else {
  // Log the status and body, then show a generic error.
}

PHP

$response = Http::withHeaders([
    'Authorization' => 'Bearer '.env('BOOKBOOST_API_KEY'),
    'Accept'        => 'application/json',
])->post('https://cdp.bookboost.io/api/v1/users/store-in-list', [
    'list_id'       => env('BOOKBOOST_NEWSLETTER_LIST_ID'),
    'consent_id'    => env('BOOKBOOST_MARKETING_CONSENT_ID'),
    'email'         => $request->input('email'),
    'first_name'    => $request->input('first_name'),
    'language_code' => 'EN',
]);

Python

import os, requestsresp = requests.post(
    "https://cdp.bookboost.io/api/v1/users/store-in-list",
    headers={
        "Authorization": f"Bearer {os.environ['BOOKBOOST_API_KEY']}",
        "Accept": "application/json",
    },
    json={
        "list_id": os.environ["BOOKBOOST_NEWSLETTER_LIST_ID"],
        "consent_id": os.environ["BOOKBOOST_MARKETING_CONSENT_ID"],
        "email": form["email"],
        "first_name": form.get("first_name"),
        "language_code": "EN",
    },
    timeout=10,
)

What the responses mean

Status

Meaning

What to show the visitor

201 Created

Profile created, added to the list, consent recorded. The body is the profile.

Double opt-in: "Check your inbox to confirm." Single opt-in: "You are subscribed."

409 Conflict

Someone with this email address has already granted this consent.

The same success message. Do not tell a visitor who is already on the list

422 Unprocessable

Validation failed, for example an invalid email address or a malformed UUID.

A field-level error on the form

404 Not Found

The list_id or consent_id does not exist in your organisation.

A generic error. This is a configuration problem, so alert your own team

401 Unauthorized

The API key is missing, invalid, expired, or revoked.

A generic error. Alert your own team

Treat 409 as a success path. It is what a visitor who is already subscribed will hit, and it is also what a retried request will hit after a network timeout.

The API allows 500 requests per minute per key. Beyond that you receive 429. Retry with exponential backoff. Retry on 5xx as well. Do not retry on other 4xx responses, because the request itself needs fixing.

Three worked setups

Newsletter with confirmed opt-in. The consent has Require Confirmation switched on, and there are two Journey campaigns: one on After double opt-in was requested carrying the Confirmation URL token, and one on After double opt-in was confirmed. In code it is a single request to users/store-in-list. That is the entire integration.

Members club or loyalty signup. Joining is the permission, so the consent does not require confirmation and one campaign runs on After consent was granted. Same request, plus tags such as members-club and signup-2026-q3 so the membership can be sliced later.

Pre-registration or waitlist. Same request again. The acknowledgement campaign runs on After consent was granted, and months later the launch message goes out as a Broadcast to the list from Bookboost, with no code involved. This is the case where the list does the real work rather than the consent.

Before you go live

  • The API key is stored server-side only, in an environment variable, and is not in version control

  • The list UUID and consent UUID are configuration rather than hardcoded strings

  • Every campaign involved is published, and none of them shows UNPUBLISHED CHANGES

  • Each campaign's consent requirement is set at its Review & Publish step

  • The confirmation email contains the Confirmation URL token, verified by sending yourself a real test

  • Tested end to end with a real address: submit, receive, click, confirm, welcome email arrives

  • Checked in Bookboost that the profile is on the list and the consent shows granted

  • 409 is handled as a success path in your form

  • language_code is being sent

  • Errors are logged on your side with the status code and the response body

  • The form has basic bot protection, because this request writes straight into the guest database

Getting help

Open Help at the bottom of the left menu and choose Talk to Us, or email support@bookboost.io. Include the response status code, the response body, and the email address used. Full endpoint details are in the Store User in List reference.

Did this answer your question?