Mobile Setup
Send events from your iOS or Android app to Hardal with an HTTP POST to the /collect endpoint.
How mobile tracking works
Mobile apps send each event to /collect on your first-party URL with an HTTP POST request. You don't need to install an SDK.
Privacy by default
You can send mobile events without attaching a known user identity. After a user grants consent, include only the identifiers allowed by your consent rules.
Consent Signals
When to send consent and what each signal changes.
Mobile collection endpoint
POST https://analytics.yourdomain.com/collect
Use your first-party URL. This is the same data-host-url value shown in the Dashboard.
| Header | Required | Value |
|---|---|---|
Content-Type | Yes | application/json |
tenant-id | Yes | Tenant ID from Setup |
x-virgo-session-id | No | Stable UUID for the current app session |
x-virgo-consent | No | granted or denied |
Create one UUID when an app session starts and reuse it in x-virgo-session-id for that session. If you omit it, Hardal creates a session ID for the request. Send x-virgo-consent: granted when identity processing is allowed and denied when it is not. If the header is omitted, Hardal reuses a valid first-party identity cache for the same Tenant ID and Source ID; without that cache, it removes userId and visitorId before processing.
Mobile event payload
{
"type": "event",
"eventName": "app_open",
"payload": {
"sourceId": "your-source-id",
"title": "Home Screen",
"hostname": "com.yourcompany.app",
"url": "app://home",
"referrer": "",
"timestamp": 1773914400000,
"timezone": "Europe/Istanbul",
"data": {
"screen_name": "HomeScreen",
"app_version": "3.2.1"
}
}
}
One event cannot contain both a non-empty items array and a non-empty leads array.
Mobile request examples
curl -X POST https://analytics.yourdomain.com/collect \
-H "Content-Type: application/json" \
-H "tenant-id: your-tenant-id" \
-H "x-virgo-session-id: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"type": "event",
"eventName": "purchase",
"payload": {
"sourceId": "your-source-id",
"title": "Order Confirmed",
"hostname": "com.yourcompany.app",
"url": "app://order/confirmation",
"timestamp": 1773914400000,
"data": {
"transaction_id": "ORD-20260615-7891",
"value": 214.98,
"currency": "USD",
"items": [
{ "item_id": "SKU-1", "item_name": "Running Shoes", "price": 134.99, "quantity": 1 }
]
}
}
}'
let url = URL(string: "https://analytics.yourdomain.com/collect")!
let sessionId = UUID()
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("your-tenant-id", forHTTPHeaderField: "tenant-id")
request.setValue(sessionId.uuidString, forHTTPHeaderField: "x-virgo-session-id")
request.httpBody = """
{
"type": "event",
"eventName": "purchase",
"payload": {
"sourceId": "your-source-id",
"title": "Order Confirmed",
"hostname": "com.yourcompany.app",
"url": "app://order/confirmation",
"timestamp": 1773914400000,
"data": {
"transaction_id": "ORD-20260615-7891",
"value": 214.98,
"currency": "USD",
"items": [
{ "item_id": "SKU-1", "item_name": "Running Shoes", "price": 134.99, "quantity": 1 }
]
}
}
}
""".data(using: .utf8)
URLSession.shared.dataTask(with: request).resume()
val sessionId = UUID.randomUUID()
val body = """
{
"type": "event",
"eventName": "purchase",
"payload": {
"sourceId": "your-source-id",
"title": "Order Confirmed",
"hostname": "com.yourcompany.app",
"url": "app://order/confirmation",
"timestamp": 1773914400000,
"data": {
"transaction_id": "ORD-20260615-7891",
"value": 214.98,
"currency": "USD",
"items": [
{ "item_id": "SKU-1", "item_name": "Running Shoes", "price": 134.99, "quantity": 1 }
]
}
}
}
""".trimIndent()
val request = Request.Builder()
.url("https://analytics.yourdomain.com/collect")
.addHeader("tenant-id", "your-tenant-id")
.addHeader("x-virgo-session-id", sessionId.toString())
.post(body.toRequestBody("application/json".toMediaType()))
.build()
const sessionId = "550e8400-e29b-41d4-a716-446655440000";
await fetch("https://analytics.yourdomain.com/collect", {
method: "POST",
headers: {
"Content-Type": "application/json",
"tenant-id": "your-tenant-id",
"x-virgo-session-id": sessionId
},
body: JSON.stringify({
type: "event",
eventName: "purchase",
payload: {
sourceId: "your-source-id",
title: "Order Confirmed",
hostname: "com.yourcompany.app",
url: "app://order/confirmation",
timestamp: 1773914400000,
data: {
transaction_id: "ORD-20260615-7891",
value: 214.98,
currency: "USD",
items: [
{ item_id: "SKU-1", item_name: "Running Shoes", price: 134.99, quantity: 1 }
]
}
}
})
});
final sessionId = "550e8400-e29b-41d4-a716-446655440000";
await http.post(
Uri.parse("https://analytics.yourdomain.com/collect"),
headers: {
"Content-Type": "application/json",
"tenant-id": "your-tenant-id",
"x-virgo-session-id": sessionId
},
body: jsonEncode({
"type": "event",
"eventName": "purchase",
"payload": {
"sourceId": "your-source-id",
"title": "Order Confirmed",
"hostname": "com.yourcompany.app",
"url": "app://order/confirmation",
"timestamp": 1773914400000,
"data": {
"transaction_id": "ORD-20260615-7891",
"value": 214.98,
"currency": "USD",
"items": [
{"item_id": "SKU-1", "item_name": "Running Shoes", "price": 134.99, "quantity": 1}
]
}
}
}),
);
var sessionId = Guid.NewGuid().ToString();
var payload = @"{
""type"": ""event"",
""eventName"": ""purchase"",
""payload"": {
""sourceId"": ""your-source-id"",
""title"": ""Order Confirmed"",
""hostname"": ""com.yourcompany.app"",
""url"": ""app://order/confirmation"",
""timestamp"": 1773914400000,
""data"": {
""transaction_id"": ""ORD-20260615-7891"",
""value"": 214.98,
""currency"": ""USD"",
""items"": [
{ ""item_id"": ""SKU-1"", ""item_name"": ""Running Shoes"", ""price"": 134.99, ""quantity"": 1 }
]
}
}
}";
using var request = new UnityWebRequest("https://analytics.yourdomain.com/collect", "POST");
request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(payload));
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("tenant-id", "your-tenant-id");
request.SetRequestHeader("x-virgo-session-id", sessionId);
await request.SendWebRequest();
The endpoint and payload match the API & Server-side setup. Only the sender changes: here, the request comes from your app instead of your backend.
A valid request returns 202 Accepted. Use the response Event ID and Request ID when troubleshooting.