risenexa-tracking
Ruby SDK for tracking user events with Risenexa. Report user registrations and conversions to your Risenexa dashboard with a single method call.
- Zero runtime dependencies — uses Ruby's built-in
Net::HTTP - Idempotent retries — UUID v4 anchor prevents duplicate counts on retry
- Typed errors — distinct error classes for auth, validation, rate limiting, and network failures
- Two configuration modes — global (Rails initializer) or per-instance (multi-startup)
For the full behavioral specification, see SDK-SPEC.md.
Installation
Add this line to your application's Gemfile:
gem "risenexa-tracking"
Then run:
bundle install
Or install directly:
gem install risenexa-tracking
Requirements: Ruby >= 3.1.0
Quick Start
Global Configuration (recommended for Rails)
Create an initializer at config/initializers/risenexa_tracking.rb:
Risenexa::Tracking.configure do |config|
config.api_key = ENV["RISENEXA_API_KEY"] # Bearer token with tracking:write scope
config.startup_slug = ENV["RISENEXA_STARTUP_SLUG"] # Your startup's slug
end
Then call the module-level convenience methods anywhere in your app:
# After a user signs up
Risenexa::Tracking.track_registration(user_id: current_user.id.to_s)
# After a user starts paying
Risenexa::Tracking.track_conversion(user_id: current_user.id.to_s)
Per-Instance Configuration
Useful when managing multiple startups from a single Rails app:
startup_a = Risenexa::Tracking::Client.new(
api_key: "rxt_live_abc123",
startup_slug: "startup-alpha"
)
startup_b = Risenexa::Tracking::Client.new(
api_key: "rxt_live_xyz789",
startup_slug: "startup-beta"
)
startup_a.track_registration(user_id: "usr_1")
startup_b.track_conversion(user_id: "usr_2")
Per-instance clients are fully independent — no shared state with global configuration or other instances.
API Reference
Convenience Methods
track_registration(user_id:, **opts)
Sends event_type: "user_registered", action: "add".
result = client.track_registration(user_id: "usr_123")
result.success? # => true
result.status_code # => 202
result.event_id # => "550e8400-e29b-41d4-a716-446655440000"
result.body # => {"status" => "accepted"}
track_conversion(user_id:, **opts)
Sends event_type: "user_converted", action: "add".
result = client.track_conversion(user_id: "usr_123")
Both methods accept optional keyword arguments:
| Option | Type | Default | Description |
|---|---|---|---|
event_id: |
String | auto UUID v4 | Idempotency anchor |
occurred_at: |
String | server time | ISO 8601 UTC timestamp |
metadata: |
Hash | {} |
Arbitrary JSONB payload |
action: |
String | "add" |
"add" or "remove" |
Low-Level track Method
Use when you need full control over all HTTP contract fields:
client.track(
event_type: "user_registered",
user_id: "usr_123",
event_id: "550e8400-e29b-41d4-a716-446655440000", # optional
occurred_at: "2026-04-01T12:00:00Z", # optional
metadata: { plan: "pro", source: "google" }, # optional
action: "add" # optional
)
Configuration Options
| Option | Required | Default | Type | Description |
|---|---|---|---|---|
api_key |
Yes | — | String | Bearer token with tracking:write scope |
startup_slug |
Yes | — | String | Slug identifying the startup |
base_url |
No | "https://app.risenexa.com" |
String | API base URL (override for staging) |
timeout |
No | 2000 |
Integer | Per-request timeout in milliseconds |
max_retries |
No | 3 |
Integer | Maximum retry attempts (0 disables retries) |
Error Handling
All error classes inherit from Risenexa::Tracking::Error < StandardError.
begin
client.track_registration(user_id: "usr_123")
rescue Risenexa::Tracking::AuthenticationError => e
# HTTP 401 — missing or invalid API key; check your api_key
puts e.
rescue Risenexa::Tracking::AuthorizationError => e
# HTTP 403 — token lacks tracking:write scope
puts e.
rescue Risenexa::Tracking::StartupNotFoundError => e
# HTTP 404 — wrong startup_slug; check your configuration
puts e.
rescue Risenexa::Tracking::ValidationError => e
# HTTP 422 — invalid payload
puts e.errors.inspect # Array of error strings from response body
rescue Risenexa::Tracking::RateLimitError => e
# All retries exhausted on 429
puts "Retry after #{e.retry_after} seconds"
rescue Risenexa::Tracking::MaxRetriesExceededError => e
# All retries exhausted on 500/502/503
puts "Last response: #{e.last_response.code}"
rescue Risenexa::Tracking::ConnectionError => e
# All retries exhausted due to network/timeout failures
puts "Transport error: #{e.cause.class}"
rescue Risenexa::Tracking::ConfigurationError => e
# Missing api_key or startup_slug — caught before any HTTP request
puts e.
end
Error Classes
| Class | HTTP Status | Notes |
|---|---|---|
AuthenticationError |
401 | Never retried |
AuthorizationError |
403 | Never retried |
StartupNotFoundError |
404 | Never retried |
ValidationError |
422 | Never retried; .errors contains array from response |
RateLimitError |
429 (exhausted) | .retry_after has seconds from Retry-After header |
MaxRetriesExceededError |
5xx (exhausted) | .last_response has the last Net::HTTPResponse |
ConnectionError |
timeout/refused | .cause has the underlying transport exception |
ConfigurationError |
— | Raised before HTTP; missing api_key or startup_slug |
Retry Behavior
The SDK retries on transient errors (429, 500, 502, 503, timeouts, connection failures) with exponential backoff and ±20% jitter:
| Before Retry | Base Delay | Jitter Range | Actual Range |
|---|---|---|---|
| Retry 1 | 1.0s | ±0.2s | [0.8s, 1.2s] |
| Retry 2 | 2.0s | ±0.4s | [1.6s, 2.4s] |
| Retry 3 | 4.0s | ±0.8s | [3.2s, 4.8s] |
Idempotency: The SDK generates a UUID v4 event_id before the first attempt and reuses it across all retries. This ensures the server counts the event exactly once even if the SDK retries.
Disabling retries:
client = Risenexa::Tracking::Client.new(
api_key: "rxt_live_abc123",
startup_slug: "my-startup",
max_retries: 0 # raise immediately on any retryable error
)
Development
git clone https://github.com/envixo/risenexa-tracking-rb.git
cd risenexa-tracking-rb
bundle install
bundle exec rspec
The test suite includes all 35 compliance tests from the SDK specification.
License
MIT License. See LICENSE.txt.