Send telemetry to Application Insights with one request: curl, PowerShell, OTLP
Application Insights accepts telemetry as JSON posted to the IngestionEndpoint from your connection string plus v2/track, so one curl or PowerShell call can record a custom event, a metric or an availability result from a script, a pipeline or a cron job. The body is an envelope with the instrumentation key, a timestamp and a data object (EventData, MetricData and so on); a good answer is HTTP 200 with itemsAccepted equal to itemsReceived. Resources that require Microsoft Entra ID take v2.1/track with a bearer token. Native OTLP ingestion is generally available, but Microsoft documents it through the OpenTelemetry Collector, not hand-made requests.
Most telemetry should come from an SDK or the OpenTelemetry Distro. But some things have no runtime to host an SDK: a deployment step, a backup script, a scheduled shell job, a smoke test. For those, one HTTP request is the whole integration, and it’s also the quickest way to prove the ingestion path works at all.
The endpoint and the envelope
The connection string tells you where to post. A typical one looks like InstrumentationKey=…;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com/;LiveEndpoint=…. Append v2/track to IngestionEndpoint. A configuration that supplies only an instrumentation key uses the global https://dc.applicationinsights.azure.com, but Microsoft ended support for instrumentation-key ingestion on March 31, 2025; use the regional endpoint.
Every item is an envelope:
| Field | Meaning |
|---|---|
name | Envelope name, such as Microsoft.ApplicationInsights.Event |
time | When it happened, UTC ISO 8601; no older than 48 hours |
iKey | The instrumentation key from the connection string |
tags | Optional context, such as ai.cloud.role for the role name |
data.baseType | EventData, MetricData, AvailabilityData, RequestData and so on |
data.baseData | The item itself; its fields depend on the type |
The type-specific fields come from the contracts the SDKs use. EventData has ver (2), name, properties (strings) and measurements (numbers). MetricData has ver (2), a metrics array of data points (name, kind, value, count, min, max, stdDev) and properties. The Microsoft OpenTelemetry Distro and SDK 3.x still convert to this schema before sending, so it’s the same format whatever you instrument with.
Send a custom event
curl on Linux or macOS, recording a deployment as a custom event with two properties:
IKEY="<instrumentation key>"
ENDPOINT="https://<region>.in.applicationinsights.azure.com/"
curl -s -X POST "${ENDPOINT}v2/track" -H "Content-Type: application/json" -d @- <<EOF
{
"name": "Microsoft.ApplicationInsights.Event",
"time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"iKey": "${IKEY}",
"tags": { "ai.cloud.role": "release-pipeline" },
"data": {
"baseType": "EventData",
"baseData": {
"ver": 2,
"name": "DeploymentCompleted",
"properties": { "service": "orders-api", "version": "2026.10.21.3" },
"measurements": { "durationSeconds": 184 }
}
}
}
EOF
The same event from PowerShell, building the body as an object so the JSON is always valid:
$cs = $env:APPLICATIONINSIGHTS_CONNECTION_STRING
$map = @{}; foreach ($part in $cs.Split(';')) { $kv = $part.Split('=', 2); if ($kv.Count -eq 2) { $map[$kv[0]] = $kv[1] } }
$body = @{
name = 'Microsoft.ApplicationInsights.Event'
time = (Get-Date).ToUniversalTime().ToString('o')
iKey = $map['InstrumentationKey']
data = @{ baseType = 'EventData'; baseData = @{ ver = 2; name = 'BackupFinished'; properties = @{ host = $env:COMPUTERNAME } } }
} | ConvertTo-Json -Depth 6
Invoke-RestMethod -Method Post -Uri ($map['IngestionEndpoint'] + 'v2/track') -ContentType 'application/json' -Body $body
Keep event names few and stable: Microsoft’s data model asks you not to create a separate name for each instance of an event, because names drive grouping. Put the variable parts in properties.
Send a metric
A single measurement needs only name and value; for a single measurement, min, max and the sum all equal the value and the count is 1. If your script has already aggregated a minute of samples, send them as one data point with count, min, max and value set to the sum.
A queue-depth reading from a cron job:
curl -s -X POST "${ENDPOINT}v2/track" -H "Content-Type: application/json" -d '{
"name": "Microsoft.ApplicationInsights.Metric",
"time": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"iKey": "'"${IKEY}"'",
"data": {
"baseType": "MetricData",
"baseData": {
"ver": 2,
"metrics": [ { "name": "QueueDepth", "kind": 0, "value": 42, "count": 1 } ],
"properties": { "queue": "orders" }
}
}
}'
kind 0 is a measurement and 1 an aggregation. A script that posts directly bypasses SDK sampling, but ingestion sampling, if it’s switched on for the resource, can still drop some types such as requests. Microsoft notes that the pipeline never samples availability results out, which is why it uses them for its own connectivity test. For metrics recorded from inside an app, use the SDK’s pre-aggregation instead of one request per value, as in custom metrics.
Send an availability result
A health check you run yourself, from a script or a machine inside your network, can report as an availability result. Microsoft’s own probe uses this type: AvailabilityData with ver 2, an id for the run, the test name, a duration written as days.hours:minutes:seconds (00.00:00:10), success, a runLocation and a message. The result appears in availabilityResults next to any standard tests, so one alert rule can watch both.
The fields of an availability result, as in Microsoft’s test script:
"data": {
"baseType": "AvailabilityData",
"baseData": {
"ver": 2,
"id": "run-20261021-0400",
"name": "Intranet login page",
"duration": "00.00:00:02",
"success": false,
"runLocation": "Office network",
"message": "HTTP 503 from the reverse proxy"
}
}
Use the test name as you would an event name: one per check, not one per run, so the availability views can group runs and compute a success rate.
Read the response and validate the JSON
A successful call returns HTTP 200 and a small JSON body. Microsoft’s check is that itemsReceived matches itemsAccepted: you sent one record, and one was accepted. A lower itemsAccepted means an item was rejected. Microsoft’s .NET troubleshooting page lists the usual reasons: required fields missing, fields over the size limits, or (from the ingestion page) a record older than 48 hours.
Limits worth validating before you post: property and metric names up to 150 characters, property values up to 8,192, messages up to 32,768, and 64 KB per telemetry item. Measurements must be numbers; strings aren’t supported.
Then confirm the item landed, by type:
customEvents
| where timestamp > ago(30m) and name == "DeploymentCompleted"
| project timestamp, cloud_RoleName, customDimensions, customMeasurements
customMetrics
| where timestamp > ago(30m) and name == "QueueDepth"
| project timestamp, value, valueCount, valueSum, customDimensions
In a workspace, the tables are AppEvents and AppMetrics, where valueCount becomes ItemCount and valueSum becomes Sum.
Classic SDK 2.x
This envelope is exactly what TelemetryClient.TrackEvent and TrackMetric produced in the 2.x SDK, which is why the old trick of capturing the SDK’s traffic and replaying it with curl worked. If a 2.x app exists alongside your scripts, prefer TrackEvent there: it fills in context (role, operation, SDK version) that a script has to add by hand in tags. The no-data checklist uses the same request as a pipeline probe.
Today: SDK 3.x, the Distro, Entra ID and OTLP
In code, use the SDK rather than raw HTTP. SDK 3.x keeps TrackEvent. With the Microsoft OpenTelemetry Distro for .NET, a custom event is an ILogger call whose message template includes the microsoft.custom_event.name attribute:
app.Logger.LogInformation("{microsoft.custom_event.name} {service}", "DeploymentCompleted", "orders-api");
Node.js and Java set the same attribute on an OpenTelemetry log record; Python passes it in the logging call’s extra.
Microsoft Entra ID. If the resource has local authentication disabled, v2/track answers 400 because it doesn’t support Entra ID. Post to v2.1/track with a token for the audience https://monitor.azure.com, issued to an identity that holds the Monitoring Metrics Publisher role on the resource:
TOKEN=$(az account get-access-token --resource https://monitor.azure.com --query accessToken -o tsv)
curl -s -X POST "${ENDPOINT}v2.1/track" -H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" -d @event.json
A 401 means no valid token; a 403 means the identity lacks the role.
OTLP. Azure Monitor’s native OTLP ingestion reached general availability in June 2026. Microsoft documents it through the OpenTelemetry Collector (version 0.132.0 or later with the Azure authentication extension), Microsoft Entra authentication, and per-signal endpoints on a data collection rule, such as …/streams/Microsoft-OTLP-Traces/otlp/v1/traces. Microsoft doesn’t document posting OTLP by hand, so for a one-off record from a script, v2/track remains the practical choice; for an app already instrumented with OpenTelemetry, point a Collector at the OTLP endpoints instead.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
itemsAccepted is 0 | Missing field, bad type, stale time | Check baseType, ver, time within 48 hours |
| HTTP 400 “Authentication not supported” | Resource requires Microsoft Entra ID | Use v2.1/track with a bearer token |
| HTTP 403 | Identity lacks Monitoring Metrics Publisher | Assign the role on the resource |
Accepted but not in customEvents | Wrong iKey or endpoint | Use both values from the same connection string |
| Event appears with the wrong role | No ai.cloud.role tag | Add it to tags |
| Metric average looks too high | Aggregated sum sent without its count | Set count to the number of samples |
Queries for this topic
-
Custom events by name as a timechart
customEvents usage
Returns: One line per custom event name for the ten most frequent events, showing the sampling-adjusted count in each hour of the last 7 days.
Frequently asked questions
Is there an official REST API for sending custom events?
Microsoft documents v2/track in its troubleshooting guidance, with PowerShell and curl examples, and the SDKs post the same envelope format. Treat the SDK contracts as the schema, keep payloads simple, and always check the counts in the response.
Can I send telemetry with a timestamp in the past?
Yes, within limits. Microsoft states the ingestion endpoint doesn't accept records older than 48 hours. Use a UTC ISO 8601 time in the envelope; items are queried by that timestamp, not by when they arrived.
Why did the endpoint return 200 but the item never appeared?
Look at the counts in the response. If itemsAccepted is lower than itemsReceived, an item was rejected, typically for a missing required field, an oversized value or a stale timestamp. Also check the resource: a different instrumentation key sends the item somewhere else.
Sources, checked
- Troubleshoot missing application telemetry (send a record with PowerShell or curl)
- Connection strings in Application Insights
- Application Insights telemetry data model
- Telemetry contracts (IEventData, IMetricData, IDataPoint) in the Application Insights JavaScript SDK
- Microsoft Entra authentication for Application Insights (token audience)
- Ingest OTLP data into Azure Monitor with the OpenTelemetry Collector
- Add and modify OpenTelemetry in Application Insights (custom events)
- Migrate to workspace-based Application Insights resources (customMetrics columns)