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 |
| Yes | UUID of the list |
| Yes | UUID of the consent |
| Yes | Valid email address, 6 to 100 characters |
| No | Up to 255 characters |
| No | Up to 255 characters |
| No | International format, for example |
| No | How to address the guest, for example |
| No | Up to 16 characters, for example |
| No | Up to 7 characters |
| No | Exactly two letters, for example |
| No | Exactly two letters, for example |
| No | Exactly two letters |
| 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 |
| 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." |
| 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 |
| Validation failed, for example an invalid email address or a malformed UUID. | A field-level error on the form |
| The | A generic error. This is a configuration problem, so alert your own team |
| 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
granted409is handled as a success path in your formlanguage_codeis being sentErrors 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.