Send events
curl --request POST \
--url https://cl.uselayers.com/beacon \
--header 'Accept: <accept>' \
--header 'Content-Type: <content-type>' \
--data '
{
"events": [
{}
]
}
'import requests
url = "https://cl.uselayers.com/beacon"
payload = { "events": [{}] }
headers = {
"Content-Type": "<content-type>",
"Accept": "<accept>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>', Accept: '<accept>'},
body: JSON.stringify({events: [{}]})
};
fetch('https://cl.uselayers.com/beacon', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://cl.uselayers.com/beacon",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'events' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Accept: <accept>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://cl.uselayers.com/beacon"
payload := strings.NewReader("{\n \"events\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
req.Header.Add("Accept", "<accept>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://cl.uselayers.com/beacon")
.header("Content-Type", "<content-type>")
.header("Accept", "<accept>")
.body("{\n \"events\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://cl.uselayers.com/beacon")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request["Accept"] = '<accept>'
request.body = "{\n \"events\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyHTTP/1.1 201 Created
Content-Type: application/json
null
{
"error": "Unauthenticated"
}
{
"errors": [
"Validation error: Required at \"events\""
]
}
Reference
Send events
POST a batch of storefront events to the Layers Tracking API. Up to 100 events per request, validated individually, dispatched to your configured pipelines.
POST
/
beacon
Send events
curl --request POST \
--url https://cl.uselayers.com/beacon \
--header 'Accept: <accept>' \
--header 'Content-Type: <content-type>' \
--data '
{
"events": [
{}
]
}
'import requests
url = "https://cl.uselayers.com/beacon"
payload = { "events": [{}] }
headers = {
"Content-Type": "<content-type>",
"Accept": "<accept>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>', Accept: '<accept>'},
body: JSON.stringify({events: [{}]})
};
fetch('https://cl.uselayers.com/beacon', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://cl.uselayers.com/beacon",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'events' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Accept: <accept>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://cl.uselayers.com/beacon"
payload := strings.NewReader("{\n \"events\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
req.Header.Add("Accept", "<accept>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://cl.uselayers.com/beacon")
.header("Content-Type", "<content-type>")
.header("Accept", "<accept>")
.body("{\n \"events\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://cl.uselayers.com/beacon")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request["Accept"] = '<accept>'
request.body = "{\n \"events\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyHTTP/1.1 201 Created
Content-Type: application/json
null
{
"error": "Unauthenticated"
}
{
"errors": [
"Validation error: Required at \"events\""
]
}
POST /beacon is the single ingestion endpoint for the Tracking API. It accepts a batch of up to 100 events per request and routes each event to the store pipelines configured to receive that event type.
Authorization
You can pass the token as a header or as a?token= query parameter. See Authentication for the full discussion.
string
Your Layers storefront access token. Required if
?token= is not used.string
Same token, passed via query string. Use this form when calling from
navigator.sendBeacon, which can’t set custom headers.Headers
string
default:"application/json"
required
string
default:"application/json"
required
string
Optional. If the value contains
tapcart or fuego (case-insensitive), the request is tagged as shopping_channel: "app". Otherwise the channel is inferred from User-Agent and defaults to "web".Body
array
required
Array of 1–100 event objects. Each event is validated independently against the schema for its
event_type. Events that fail validation are dropped without failing the batch.See Event types for the per-event schema and the Payload examples page for full request bodies.Behavior
- Returns
201 Createdwith a JSONnullbody on success. - Returns
401if no valid token is provided. - Returns
422with a{ errors: [...] }body if the top-level request shape is wrong (missingevents, empty array, more than 100 entries). - Per-event validation errors do not fail the batch. Invalid events are silently dropped. Valid events continue to dispatch.
- Each accepted event is enriched server-side with:
geo_country,geo_state,geo_city(from Cloudflare edge geo).device_type,browser,os(parsed fromUser-Agent).shopping_channel(weborapp, inferred unless the event already specifies it).
- Events are routed to your store’s configured pipelines based on
event_type. Routing is fire-and-forget. Pipeline delivery failures are logged but do not affect the HTTP response.
Limits
| Limit | Value |
|---|---|
| Max events per request | 100 |
| Min events per request | 1 |
| Recommended max request body | 64 KB (the sendBeacon browser limit) |
Request
Example request
POST https://cl.uselayers.com/beacon
Content-Type: application/json
Accept: application/json
X-Storefront-Access-Token: shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{
"events": [
{
"event_id": "01HZY8E2K3M4N5P6Q7R8S9T0V1",
"event_type": "product_click",
"timestamp": "2026-06-01T14:30:00Z",
"session_id": "sess_abc123",
"attribution_token": "2y10smI2dB7XZXXFJsLUELltgueq8NRd",
"product_id": 7003338965178,
"position": 3
},
{
"event_id": "01HZY8E2K3M4N5P6Q7R8S9T0V2",
"event_type": "add_to_cart",
"timestamp": "2026-06-01T14:30:12Z",
"session_id": "sess_abc123",
"product_id": 7003338965178,
"variant_id": 41234567890123
}
]
}
Responses
HTTP/1.1 201 Created
Content-Type: application/json
null
{
"error": "Unauthenticated"
}
{
"errors": [
"Validation error: Required at \"events\""
]
}
Next steps
- Event types — the schema for every supported
event_type. - Payload examples — copy-pasteable bodies for the most common scenarios.
- Sending events with
navigator.sendBeacon— the recommended browser pattern.
Was this page helpful?