1. Get a key
In the dashboard, open Settings, name a key after whatever will hold it, and generate it.
Keys start with bbx_live_. The full value is shown once and never again, so copy it before you
leave the page.
export BEACONBOX_API_KEY=bbx_live_...2. Install a client, if you want one
# Nothing to install. Every call is JSON over HTTPS.pip install beaconboxcomposer require beaconbox/beaconbox-phpThe Python client reads BEACONBOX_API_KEY when you pass no key. The PHP client takes the key as
its first argument and reads no environment variable, so pass it in yourself.
3. Push an update
This is the call you will spend your integration in. Everything else is optional.
curl https://api.beaconbox.eu/api/v1/messages \
-H "Authorization: Bearer $BEACONBOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4711-shipped" \
-d '{
"recipient_email": "buyer@example.com",
"subject": "Your order has shipped",
"body": "Tracking XY123456789EE. Estimated delivery Thursday.",
"reference": "#A-10294",
"order_status": "shipped",
"kind": "updateable"
}'from beaconbox import BeaconBox, OrderStatus
client = BeaconBox() # reads BEACONBOX_API_KEY
result = client.messages.push(
recipient_email="buyer@example.com",
subject="Your order has shipped",
body="Tracking XY123456789EE. Estimated delivery Thursday.",
reference="#A-10294",
order_status=OrderStatus.SHIPPED,
kind="updateable",
idempotency_key="order-4711-shipped",
)
print(result.id, result.created, result.nudged)<?php
use BeaconBox\BeaconBoxClient;
use BeaconBox\Enum\MessageKind;
use BeaconBox\Enum\OrderStatus;
use BeaconBox\Model\MessagePush;
$client = new BeaconBoxClient(getenv('BEACONBOX_API_KEY'));
$result = $client->messages->push(
new MessagePush(
recipientEmail: 'buyer@example.com',
subject: 'Your order has shipped',
body: 'Tracking XY123456789EE. Estimated delivery Thursday.',
reference: '#A-10294',
orderStatus: OrderStatus::Shipped,
kind: MessageKind::Updateable,
),
idempotencyKey: 'order-4711-shipped',
);
echo $result->id, ' ', var_export($result->created, true);{
"id": "v55favd767qu",
"recipient_email": "buyer@example.com",
"subject": "Your order has shipped",
"kind": "updateable",
"status": "active",
"created": true,
"scheduled_for": null,
"nudged": true,
"email": {
"sending": true,
"skipped_reason": null
},
"sms": {
"queued": false,
"credits": 0,
"skipped_reason": "no_phone",
"escalates_at": null
},
"sms_units": 0,
"whatsapp": {
"queued": false,
"credits": 0,
"skipped_reason": "no_phone",
"sms_fallback": false,
"escalates_at": null
},
"whatsapp_opt_in": null,
"reference": "#A-10294",
"order_status": "shipped",
"created_at": "2026-10-02T20:11:10.674762Z",
"updated_at": "2026-10-02T20:11:10.674762Z"
}That is a success, and it is worth sitting with for a moment because it is the shape of every response here.
The update is in the customer’s inbox and an email is on its way. created: true means this made
a new update rather than rewriting one, and nudged: true means a nudge was queued.
The two skipped_reason: "no_phone" entries are not errors. This account has SMS and WhatsApp
switched on, the push carried no phone number, so both paid channels reported why they did not
send and the request succeeded anyway. On an account with those channels off, sms and whatsapp
are null instead: there was nothing to decide.
That is the convention the whole API runs on, and Reading outcomes is the page about it.
4. Push it again
Send the same subject to the same recipient with kind: "updateable" and the second push
rewrites the first rather than joining it.
curl https://api.beaconbox.eu/api/v1/messages \
-H "Authorization: Bearer $BEACONBOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4711-delivered" \
-d '{
"recipient_email": "buyer@example.com",
"subject": "Your order has shipped",
"body": "Delivered and signed for at 14:20.",
"reference": "#A-10294",
"order_status": "delivered",
"kind": "updateable"
}'result = client.messages.push(
recipient_email="buyer@example.com",
subject="Your order has shipped",
body="Delivered and signed for at 14:20.",
reference="#A-10294",
order_status=OrderStatus.DELIVERED,
kind="updateable",
idempotency_key="order-4711-delivered",
)
assert result.created is False # rewritten in place
assert result.nudged is False # and quietly, by default$result = $client->messages->push(
new MessagePush(
recipientEmail: 'buyer@example.com',
subject: 'Your order has shipped',
body: 'Delivered and signed for at 14:20.',
reference: '#A-10294',
orderStatus: OrderStatus::Delivered,
kind: MessageKind::Updateable,
),
idempotencyKey: 'order-4711-delivered',
);
// $result->created === false, $result->nudged === falsecreated is now false and nudged is false. The customer’s inbox is current and nobody was
interrupted twice.
The rewrite takes its order_status and reference from the second push, so the update now reads
as delivered. Leave order_status out of a rewrite and the status is cleared: the next nudge about
this update has no status wording and no WhatsApp message.
5. Read what happened
Delivery is asynchronous, so it is not in the push response. Read the message back:
curl https://api.beaconbox.eu/api/v1/messages/v55favd767qu \
-H "Authorization: Bearer $BEACONBOX_API_KEY"message = client.messages.get("v55favd767qu")
print(message.delivery.delivered, message.delivery.opened)$message = $client->messages->get('v55favd767qu');
var_dump($message->delivery->delivered, $message->delivery->opened);Polling is fine for a handful of messages and wrong for a shop. In production, subscribe to webhooks and let BeaconBox tell you.
Where to go next
- Pushing updates for everything the push body can do.
- Reading outcomes for what the response is telling you.
- Paid channels to add SMS or WhatsApp.
