Skip to main content
Every event Boble sends to your endpoint follows the same structure. The fields that change between events are the type and what sits inside data.object. Everything else is constant.

The envelope

{
  "id": "0d6d31a3-2b9e-4d9f-9a51-3b9c3f8e6c10",
  "type": "user_promotion.access_granted",
  "api_version": "2026-01-01",
  "created": 1717497600,
  "livemode": true,
  "data": {
    "object": { /* the event-specific payload */ }
  }
}
id
string
Unique id for this delivery. A resend of the same logical event uses a brand-new id, not the original.
type
string
The event name. See Event types for the full list and what each one means.
api_version
string
Schema version of the payload. Currently always 2026-01-01. If the schema ever evolves, the new version will be announced and the old one will keep working for a transition period.
created
number
Unix timestamp in seconds, marking when Boble emitted the event.
livemode
boolean
true in production, false in any test environment (including a test event you triggered manually outside production).
data.object
object
The event-specific payload. Its shape depends on type. The full layout for each event is in Event types.

The headers

Boble adds three headers on every delivery:
HeaderValue
Content-Typeapplication/json
User-AgentBoble-Webhook/1.0 — useful to filter Boble traffic in your access logs.
Boble-SignatureThe HMAC signature of the body. See Signature verification.
Boble-Event-IdMatches the id in the body. A resend of an event uses a new id.

Same buyer everywhere

All user_promotion.* events share the same nested user and promotion objects. This means once you’ve written code (or a Zap) to pull the buyer’s email out of one event, the same path works on every event.

user object

FieldDescription
idUUID of the buyer.
emailBuyer’s email address. May be null if the account was deleted.
firstnameFirst name. May be null.
lastnameLast name. May be null.
usernameUsername on Boble. May be null.
avatarFull URL of the avatar. null when not set.
preferred_languageIETF code: en, fr, es. May be null.
created_atISO-8601 timestamp of the buyer’s account creation. May be null.

promotion object

FieldDescription
idUUID of the promotion.
titleTitle of the promotion.
typefree, one_time, or subscription.
currencyISO-4217 code in uppercase: EUR, USD, GBP.
amountPrice in the promotion’s currency. null for free promotions.

Same id across the lifecycle

For a given buyer/promotion pair, the data.object.id stays the same across every event. The first access_granted and a later canceled for the same access will carry the same data.object.id. Use it if you need to track a buyer’s journey across events. The top-level id (and the Boble-Event-Id header) is unique per delivery — a different one on every event, and a different one again on every resend.

Avoiding double application

Boble does not retry automatically, but a manual resend dispatches a brand-new delivery with the same data.object but a different envelope id. If your handler has side effects you don’t want to apply twice (sending a receipt, granting an external role), key your idempotency on a value derived from data.object — typically data.object.id combined with the event type. That way, the original and any resend collapse into the same idempotency key.