Create a write key
-
Step 1. Ask your agent to create a product analytics source for this project.
Prompt textCreate a product analytics source named iOS app for the production environment. Show me the write key. -
Step 2. Copy the key from the response. It starts with
pda_and is shown once. It can write events into this one project and nothing else, so you can ship it inside a mobile or browser application.
Send one event
Send the key as a bearer token. Only event is required.
curl https://ingest.postdeploy.dev/v1/events \
-H "Authorization: Bearer pda_YOUR_WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "onboarding_completed",
"distinct_id": "anon_123",
"properties": {
"platform": "ios",
"app_version": "1.4.0",
"variant": "onboarding_b"
}
}'
PostDeploy answers 202 with {"success": true, "accepted": 1}. Open Analytics, then Live events, to watch it arrive.
Send a batch
Batch up to 100 events in one request. Send the whole batch or none of it: one invalid event refuses the request, so you always know what still needs sending.
curl https://ingest.postdeploy.dev/v1/events/batch \
-H "Authorization: Bearer pda_YOUR_WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "event": "onboarding_started", "distinct_id": "anon_123" },
{ "event": "onboarding_completed", "distinct_id": "anon_123" }
]
}'
JavaScript
Never block a person's interaction on analytics. Send it and move on.
function capture(event, distinctId, properties) {
fetch("https://ingest.postdeploy.dev/v1/events", {
method: "POST",
headers: {
"Authorization": "Bearer " + PD_WRITE_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ event, distinct_id: distinctId, properties }),
keepalive: true
}).catch(() => {})
}
capture("paywall_viewed", anonId, { source: "onboarding" })
Swift
func capture(_ event: String, distinctId: String, properties: [String: Any] = [:]) {
var request = URLRequest(url: URL(string: "https://ingest.postdeploy.dev/v1/events")!)
request.httpMethod = "POST"
request.setValue("Bearer \(writeKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: [
"event": event,
"distinct_id": distinctId,
"properties": properties
])
URLSession.shared.dataTask(with: request).resume()
}
Task { capture("meal_logged", distinctId: anonId, properties: ["source": "camera"]) }
Kotlin
suspend fun capture(event: String, distinctId: String, properties: Map<String, Any> = emptyMap()) {
val payload = JSONObject(mapOf(
"event" to event,
"distinct_id" to distinctId,
"properties" to JSONObject(properties)
)).toString()
val request = Request.Builder()
.url("https://ingest.postdeploy.dev/v1/events")
.header("Authorization", "Bearer $writeKey")
.post(payload.toRequestBody("application/json".toMediaType()))
.build()
runCatching { client.newCall(request).execute().close() }
}
Ruby
def capture(event, distinct_id, properties = {})
uri = URI("https://ingest.postdeploy.dev/v1/events")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("PD_WRITE_KEY")}"
request["Content-Type"] = "application/json"
request.body = JSON.generate(event: event, distinct_id: distinct_id, properties: properties)
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
rescue StandardError
nil
end
Elixir
def capture(event, distinct_id, properties \\ %{}) do
Task.start(fn ->
Req.post("https://ingest.postdeploy.dev/v1/events",
auth: {:bearer, System.fetch_env!("PD_WRITE_KEY")},
json: %{event: event, distinct_id: distinct_id, properties: properties}
)
end)
end
Several apps, one project
Keep the applications of one product in one project. Ask your agent for a write key per application: one for iOS, one for Android, one for your website. Each key rotates and revokes on its own.
They share one event pool, which is the point. Send the same distinct_id from every application and a person who signs up on the web and acts in the app counts once in a funnel.
Every screen then offers an App picker, and every MCP read takes an optional app, so you can look at one application alone or at all of them together.
Create a product analytics source named Android app for the production environment. Show me the write key.
What to track
Track meaningful state changes, not interface noise. onboarding_completed, meal_logged, trial_started and subscription_started each answer a product question. button_clicked, modal_opened and screen_scrolled do not, and they crowd out the events that do.
Use the same name everywhere, in lower case with underscores. Put what varies in properties: platform, app_version, variant, source, plan, country.
Privacy
PostDeploy Analytics is designed for behavioural event data. Do not send sensitive personal information in event names, identifiers, or properties.
That means no names, email addresses, phone numbers, precise addresses, message content, health information, photos, payment details or authentication tokens.
distinct_id is whatever your application sends. Use an opaque identifier you generate. PostDeploy never derives an identity from an IP address, sets no cookie, fingerprints no device, and never joins one project's events to another's. The IP address of an ingest request is used to serve the request and is not stored on the event.
Limits
- Event name
-
1 to 120 characters: letters, digits,
_,.,:,-. - Distinct ID
- Up to 200 characters. Optional, but unique-user counts need it.
- Properties
- Up to 40 per event. Names up to 64 characters, text values up to 512, 8 KB in total.
- Property values
- Text, numbers, or true/false. Nested objects and lists are refused.
- Batch
- Up to 100 events, 256 KB per request.
- Timestamp
- RFC 3339, within the last 30 days and not in the future. Defaults to arrival time.
Retries and duplicates
Retry on 429, on any 5xx, and on a network failure, with exponential backoff. Honour Retry-After when it is present.
Do not retry 400, 401 or 403: the request will not succeed until you change it.
Send your own id on each event to make a retry safe. PostDeploy writes an event with an id it has already stored exactly once, so a retried batch does not inflate your counts. A repeated event still counts once against your monthly allowance.
{
"id": "evt_0192f3c4d5e6",
"event": "subscription_started",
"distinct_id": "anon_123"
}
Error responses
invalid_event- 400. The event name is missing or malformed.
invalid_payload- 400. The body is not the JSON shape this endpoint accepts.
invalid_properties- 400. A property name, value, or count is outside the limits.
invalid_timestamp- 400. The timestamp is malformed, too old, or in the future.
batch_too_large- 400. More than 100 events in one batch.
invalid_api_key- 401. The key is missing, unknown, revoked, or not a product analytics key.
plan_inactive- 402. The organization has no active PostDeploy access.
body_too_large- 413. The request body is over 256 KB.
rate_limit_exceeded-
429. Back off and honour
Retry-After.
Past your monthly allowance PostDeploy keeps answering 202 and reports what it dropped: {"accepted": 0, "dropped": 1, "dropped_reason": "quota"}. Your application never breaks because of an analytics limit.