VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Rust library
Check access
Receive webhooks
Persons and events
Custom payment providers

Persons and events

Create and update persons, capture events, describe groups and send push notifications from Rust.

Rust code for persons, events and push notifications, using a configured client.

Persons

Create a person

use voidhash::generated::types::CreatePersonBodyJsonEncoding;

let person = voidhash
    .persons()
    .create(&CreatePersonBodyJsonEncoding {
        distinct_id: "user_123".parse()?,
        email: Some("ada@example.com".into()),
        name: Some("Ada".into()),
    })
    .await?;

println!("{}", person.id); // person_…

email, name and created_at are Options.

Find a person

get_by_distinct_id fails with a not_found Error::Api for an unknown distinct ID:

match voidhash.persons().get_by_distinct_id("user_123").await {
    Ok(person) => println!("{}", person.id),
    Err(error) if error.is_not_found() => println!("never identified"),
    Err(error) => return Err(error.into()),
}

get fails the same way for an unknown or erased ID.

let person = voidhash.persons().get("person_k2v9x4m8q1r3").await?;

entitlements takes the same ID; list returns only the first page of persons.

Update attributes

set_attributes sets attributes but fails with person_not_found for a new distinct ID, so create the person first.

use serde_json::{json, Map, Value};
use voidhash::PersonAttributes;

let object = |value: Value| -> Map<String, Value> { value.as_object().cloned().unwrap_or_default() };

voidhash
    .persons()
    .set_attributes(&PersonAttributes {
        distinct_id: "user_123".into(),
        traits: Some(object(json!({ "plan": "team", "seats": 5 }))),
        set_once: Some(object(json!({ "signupSource": "website" }))),
        ..PersonAttributes::default()
    })
    .await?;

Capture events

Event::new(event, distinct_id, timestamp) has the setters .property, .properties, .context_property, .context, .uuid and .session_id.

Create the client with ingest_url set to "https://api.voidhash.com"; the default capture host doesn't answer yet.

use voidhash::Event;

voidhash
    .event_capture()
    .capture(
        &Event::new("report_exported", "user_123", chrono::Utc::now())
            .property("format", "csv")
            .property("rows", 1200),
    )
    .await?;

capture_batch sends a slice in one request; an empty slice sends nothing.

use voidhash::Event;

let now = chrono::Utc::now();
let result = voidhash
    .event_capture()
    .capture_batch(&[
        Event::new("trial_reminder_sent", "user_123", now),
        Event::new("trial_reminder_sent", "user_456", now),
    ])
    .await?;

println!("{} accepted, {} rejected", result.accepted, result.rejected);

Errors are an Error::Api whose code is an event capture code.

Groups

Groups are what your users belong to, such as their company. Name an event's groups with .group(group_type, group_key); they are sent as its $groups property:

use voidhash::Event;

voidhash
    .event_capture()
    .capture(
        &Event::new("report_exported", "user_123", chrono::Utc::now()).group("company", "acme"),
    )
    .await?;

group_identify sets a group's properties, such as what your backend knows of a company's plan and seats. .set_once_property fills only properties the group has no value for, and a later change of a property wins. Without .timestamp, the change is dated now.

use voidhash::GroupIdentify;

voidhash
    .event_capture()
    .group_identify(
        &GroupIdentify::new("company", "acme")
            .property("name", "Acme")
            .property("plan", "pro")
            .property("seats", 40)
            .set_once_property("first_plan", "trial"),
    )
    .await?;

The $groupidentify event it sends belongs to no person.

Send push notifications

Early access

Push notifications are in early access. Ask us on Discord, or email support@voidhash.com on a paid plan, to turn them on for your team.

notifications().send sends no idempotency key, so a retry after a timeout can notify twice.

use serde_json::{json, Map, Value};
use voidhash::generated::types::CreatePushNotificationBodyJsonEncoding;

let data: Map<String, Value> = [("screen".to_owned(), json!("paywall"))].into_iter().collect();

let sent = voidhash
    .notifications()
    .send(&CreatePushNotificationBodyJsonEncoding {
        distinct_ids: Some(vec!["user_123".into()]),
        person_ids: None,
        title: "Your trial ends tomorrow".parse()?,
        body: "Keep Pro for $9.99 a month.".parse()?,
        data: Some(data),
        sound: None,
        badge: None,
        priority: None,
        ttl_seconds: None,
        channel_id: None,
        collapse_id: None,
    })
    .await?;

println!("{} {} {:?}", sent.id, sent.device_count, sent.unresolved_distinct_ids);

priority takes Default or High; see Send a push for the other fields.

Next steps

  • Receive webhooks
  • People and identity
  • Events

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousReceive webhooksNextCustom payment providers

On this page

PersonsCreate a personFind a personUpdate attributesCapture eventsGroupsSend push notificationsNext steps