Metadata
Put your own ids on a text with metadata, and BlueBot hands them back on the message and on every webhook about it, so each delivery, failure and read ties back to your record.
Metadata is your own data on a text. BlueBot never reads it or acts on it; it stores it with the text and hands it back on the message and on every event about that text. Use it to tie a text to the record in your system it was sent for, so a webhook saying “this text failed” tells you which appointment, order or lead it was about.
Dr. Patel’s booking system texts each patient a reminder the day before. It puts the appointment’s id in metadata:
/v1/messagescurl -X POST "https://usebluebot.com/api/v1/messages" \
-H "Authorization: Bearer $BLUEBOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: appt-7712-reminder" \
-d '{
"to": "+15555550123",
"text": "Hi Maria, a reminder that your cleaning with Dr. Patel is tomorrow at 10:30 AM.",
"metadata": {
"appointment_id": "7712",
"office": "north"
}
}'{
"id": "msg_2Bf8QzD4kJmA6nXwEuTr1c",
"object": "message",
"direction": "out",
"state": "queued",
"to": "+15555550123",
"metadata": {
"appointment_id": "7712",
"office": "north"
}
}When the text fails, the message.failed webhook carries the same metadata, so the booking system can flag appointment 7712 for a phone call without looking anything up.
What it can hold
| Limit | |
|---|---|
| Keys | Up to 20 |
| A key’s name | 1 to 40 characters |
| A value | A string of up to 500 characters |
Every value is a string. Send a number as "7712", not 7712, and a list or an object as a string you encode yourself, such as "a,b,c". A number, a true, a null, a nested object or anything over a limit makes the whole send 400 invalid_request, with details naming the field, such as metadata.appointment_id. Nothing is sent.
Metadata is for ids and short labels. It's sent with every event about the text, so keep it small: the id of your record rather than a copy of it.
Where it comes back
| Where | What you get |
|---|---|
| The send’s answer | The metadata you sent. |
GET /v1/messages/{id} and GET /v1/messages | The text’s metadata, on every text in a list. |
Every message.* event, by webhook or from GET /v1/events | The text’s metadata inside data, as it was when the event happened. |
Tapback events (reaction.*) | Not there. They carry message_id; read the text for its metadata. |
| Texts that come in | {}. A person’s reply has no metadata of yours. |
A text with no metadata has "metadata": {}, never null, so your code can read a key without checking for the object first.
In a webhook handler
const event = client.webhooks.verify(body, headers, process.env.BLUEBOT_WEBHOOK_SECRET!);
if (event.type === 'message.failed') {
const appointmentId = event.data.metadata.appointment_id;
if (appointmentId) await flagForCall(appointmentId, event.data.failure?.reason);
}event = client.webhooks.verify(body, headers, os.environ["BLUEBOT_WEBHOOK_SECRET"])
if event.type == "message.failed":
appointment_id = event.data.metadata.get("appointment_id")
if appointment_id:
flag_for_call(appointment_id, event.data.failure.reason if event.data.failure else None)The SDKs send and return metadata keys exactly as you write them. TypeScript turns the API’s own fields into camelCase (event.data.contactId) but leaves the keys inside metadata alone, so appointment_id stays appointment_id.
Matching a reply to your record
A reply has no metadata, because the person wrote it, not your code. To find which of your records it belongs to, read the last text you sent that person and take its metadata:
/v1/messagescurl "https://usebluebot.com/api/v1/messages?person=%2B15555550123&direction=out&limit=1" \
-H "Authorization: Bearer $BLUEBOT_API_KEY"The first text in data is the newest you sent them, and its metadata.appointment_id says which appointment Maria is answering. When you text the same person about more than one thing at a time, this picks the newest, which may not be the one they mean; read the words of the reply too.
Changing it
You can change metadata only while a text is scheduled. PATCH /v1/messages/{id} with metadata replaces all of it, so send every key you want to keep:
/v1/messages/msg_2Bf8QzD4kJmA6nXwEuTr1ccurl -X PATCH "https://usebluebot.com/api/v1/messages/msg_2Bf8QzD4kJmA6nXwEuTr1c" \
-H "Authorization: Bearer $BLUEBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"appointment_id": "7712",
"office": "south"
}
}'Once a text is queued, its metadata is fixed. covers what else can change and when.
Settling an unconfirmed text with send_again queues a new text with the same words, files and metadata, so the new text’s events still tie back to your record.
What you can’t do with it
- Search by it. No list filters on metadata. Keep the BlueBot message
idon your own record when you send, and look the text up by its id later. - Use it as a key for retries. Two sends with the same metadata are two texts. Use an to keep a retry from sending twice.
What not to put in it
Metadata goes to every webhook address you’ve subscribed to message.* events and to anyone with a key that can read messages. BlueBot deletes the words of a text after 30 days, but its metadata stays with the rest of its record. So put an id in it and keep the details in your own system:
- No passwords, keys or tokens.
- No health, financial or other private details about the person.
"appointment_id": "7712"is fine;"reason": "root canal follow-up"is not. - Nothing you would want deleted along with the words.
In no-code apps
The Zapier, Make, n8n and Pipedream Send Text actions each take metadata as key and value pairs, and their triggers for a text’s status hand it back, so a Zap can carry a row’s id from the send to the step that updates the row.