Rust
Send vTilt events from Rust code (Axum, Actix, Rocket, plain Tokio) by POSTing to the ingest API.
There's no native Rust SDK yet, but vTilt's wire format is a plain JSON POST so any Rust code can emit events. The pattern below uses reqwest + serde_json; the same shape works inside Axum, Actix, Rocket, Tower, and Tokio binaries.
#1. Add dependencies
# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
tracing = "0.1"
once_cell = "1"toml
#2. Capture client
A OnceCell singleton built on reqwest::Client. Captures are spawned onto the runtime as fire-and-forget tasks so handlers never block on the HTTP call.
// src/vtilt.rs
use once_cell::sync::OnceCell;
use serde::Serialize;
use serde_json::Value;
use std::env;
use std::time::Duration;
#[derive(Serialize)]
struct Event<'a> {
api_key: &'a str,
event: &'a str,
distinct_id: &'a str,
properties: Value,
}
struct Vtilt {
client: reqwest::Client,
token: String,
host: String,
}
static VTILT: OnceCell<Vtilt> = OnceCell::new();
fn vtilt() -> &'static Vtilt {
VTILT.get_or_init(|| Vtilt {
client: reqwest::Client::builder()
.timeout(Duration::from_secs(2))
.build()
.expect("vtilt client"),
token: env::var("VTILT_TOKEN").expect("VTILT_TOKEN missing"),
host: env::var("VTILT_HOST").unwrap_or_else(|_| "https://www.vtilt.com".to_owned()),
})
}
pub fn capture(distinct_id: impl Into<String>, event: impl Into<String>, properties: Value) {
let distinct_id = distinct_id.into();
let event = event.into();
tokio::spawn(async move {
let v = vtilt();
let body = Event {
api_key: &v.token,
event: &event,
distinct_id: &distinct_id,
properties,
};
if let Err(e) = v.client.post(format!("{}/api/e", v.host)).json(&body).send().await {
tracing::warn!(?e, "vtilt capture failed");
}
});
}
pub fn identify(distinct_id: impl Into<String>, properties: Value) {
capture(distinct_id, "$identify", serde_json::json!({ "$set": properties }));
}rust
#3. Capture events
use serde_json::json;
vtilt::identify("user_42", json!({ "email": "alice@example.com", "plan": "pro" }));
vtilt::capture("user_42", "purchase_completed", json!({
"amount": 99.99,
"currency": "USD",
}));rust
#4. Axum handler
use axum::{Json, response::Json as JsonResponse};
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct Checkout { user_id: String, amount: f64 }
async fn checkout(Json(req): Json<Checkout>) -> JsonResponse<serde_json::Value> {
vtilt::capture(req.user_id, "purchase_completed", json!({ "amount": req.amount }));
JsonResponse(json!({ "ok": true }))
}rust
#Next steps
- Identify & alias — full identification model.
- Event forwarding — fan events out to GA4, Meta CAPI, PostHog.
- Reverse proxy — block-resistant ingestion.