Direct API Installation
Install Prevenue Direct API for backend source-of-truth events.
Direct API is the source-of-truth path for backend events that are confirmed by durable product, billing, setup, or integration state. Use it when the event should be emitted from server code after a database write, provider confirmation, billing update, OAuth completion, background job, or metered usage calculation.
This is different from the Web SDK. The Web SDK is browser-safe supplemental instrumentation for visible user intent and UI friction. Direct API uses a secret ingest key and must never run in browser, mobile, desktop, or public client code.
Create an ingest key
In Prevenue, open Settings > Integrations > Direct API and create a key. The secret is shown once.
Store it only in backend or server environment variables:
SAASFUNNELS_INGEST_API_KEY=<SAASFUNNELS_INGEST_KEY>Never expose this key in client code, frontend bundles, mobile apps, desktop apps, public repositories, logs, analytics tools, or AI prompts.
Use developer tools
Use SaaSFunnels CLI and SaaSFunnels MCP Server to speed up Direct API setup without exposing real keys.
Validate a payload before shipping it:
saasfunnels events validate ./saasfunnels-event.json --source direct --jsonSend one smoke event from a server-safe environment:
SAASFUNNELS_INGEST_API_KEY=<SAASFUNNELS_INGEST_KEY> saasfunnels events send-test --file ./saasfunnels-event.json --jsonInstall a coding-agent handoff file:
saasfunnels agent install --target codexStart MCP when an agent needs Prevenue context:
SAASFUNNELS_API_KEY=<DEVELOPER_READ_KEY> saasfunnels mcp serveMCP is read-only by default. Enable send_test_event only for explicit setup smoke testing.
Send a server event
Send events to:
https://app.prevenue.ai/api/events/ingestUse the ingest key as a bearer token:
curl -X POST https://app.prevenue.ai/api/events/ingest \
-H "Authorization: Bearer <SAASFUNNELS_INGEST_KEY>" \
-H "Content-Type: application/json" \
-d '{
"event_id": "evt_direct_001",
"event_name": "usage_limit_hit",
"account_id": "acct_123",
"user_id": "user_456",
"timestamp": "2026-06-20T15:00:00.000Z",
"semantic_type": "limit_friction",
"event_kind": "friction_event",
"sentiment": "negative",
"sentiment_score": -0.8,
"sentiment_confidence": 0.9,
"sentiment_source": "explicit",
"value": 1,
"properties": {
"plan": "starter",
"limit_name": "monthly_events",
"usage_ratio": 0.93
}
}'Minimum viable SaaS account facts
Prevenue should learn a small account profile while events arrive. Send these as bounded properties on normal account-scoped events, especially signup, login, activation, plan change, team, and usage rollup events.
| Fact | Preferred field names | Notes |
|---|---|---|
| Customer signup date | account_created_at, signup_at, or customer_created_at | This is the customer's product signup date, not the time Prevenue first saw the account. |
| Login activity | user_logged_in or session_started events with semantic_type: "login" | Include user_id when a user caused the login. Prevenue can derive login counts from these events. |
| Plan and lifecycle | plan, plan_name, current_plan, account_status, or subscription_status | Keep values bounded and stable, such as starter, growth, active, trialing, or canceled. |
| Trial state | is_in_trial, trial_started_at, trial_ends_at | Use this when Stripe is not connected or before Stripe is available. Stripe is authoritative for billing and trial state when connected. |
| Team and seats | user_count, active_user_count, seat_count, or member_count | Send current counts from backend state or a scheduled rollup, not raw user lists. |
Example login event with account facts:
{
"event_id": "login:acct_123:user_456:2026-06-20T15:00:00.000Z",
"event_name": "user_logged_in",
"account_id": "acct_123",
"user_id": "user_456",
"timestamp": "2026-06-20T15:00:00.000Z",
"semantic_type": "login",
"event_kind": "login",
"properties": {
"account_created_at": "2026-06-01T12:00:00.000Z",
"plan": "starter",
"is_in_trial": true,
"trial_ends_at": "2026-06-28T12:00:00.000Z",
"user_count": 7,
"seat_count": 10,
"active_user_count": 6
}
}Refresh these facts on lifecycle changes or daily account rollups. Do not create noisy page-view events just to refresh profile data.
Usage-based and API-heavy products
For API-heavy products, do not forward every customer API request to Prevenue. Send aggregate usage from a backend scheduled job instead.
The recommended unit is one aggregate event per account, product, metric, and closed time bucket:
{
"event_id": "usage_rollup:acct_123:api_platform:api_calls:2026-06-01",
"event_name": "usage_rollup_recorded",
"account_id": "acct_123",
"timestamp": "2026-06-02T00:10:00.000Z",
"semantic_type": "usage",
"event_kind": "product_action",
"value": 148253,
"properties": {
"product_key": "api_platform",
"metric_key": "api_calls",
"unit": "request",
"usage_start": "2026-06-01T00:00:00.000Z",
"usage_end": "2026-06-02T00:00:00.000Z",
"aggregation_grain": "day",
"source_system": "usage_metering"
}
}Use a cron job, queue worker, warehouse job, or metering service to calculate these rows from the customer's source-of-truth usage table. Daily buckets are enough for most revenue signals. Hourly buckets are useful when usage changes quickly, a customer is near a limit, or the revenue team needs same-day routing.
The aggregate payload should answer:
| Question | Field |
|---|---|
| Which customer used it? | account_id |
| Which product surface was used? | properties.product_key |
| Which usage meter changed? | properties.metric_key |
| How much usage happened? | value |
| What unit is the value in? | properties.unit |
| Which usage period does it cover? | properties.usage_start and properties.usage_end |
| Can the row be safely retried? | Stable event_id |
Keep product_key, metric_key, and unit stable. Treat them like a small product and metric catalog. For example, api_platform + api_calls + request should keep the same meaning over time. If pricing or packaging changes, add properties such as plan, tier, or pricing_version; do not silently change the unit behind an existing metric key.
Prevent overload
Aggregate before sending. A customer with millions of API requests should usually send hundreds or thousands of aggregate rows, not millions of Direct API calls. In the standard Direct API path, each request should represent an aggregate row; for very large row counts, use a bulk import plan instead of increasing concurrency.
Use these guardrails:
- Send only closed buckets, such as the previous hour or previous day.
- Use deterministic
event_idvalues so retries are treated as the same logical event instead of duplicating usage. - Retry failed rows with backoff and the same
event_id. - Split backfills into small date ranges and send oldest closed periods first.
- Keep each request compact. The Direct API accepts small JSON event payloads, not raw logs or large arrays.
- Start scheduled jobs with low concurrency, then increase only after Activity shows healthy accepts and low rejection rates.
- Send milestone or threshold events separately, such as
quota_blocked,limit_hit,overage_started, orplan_upgraded.
If the customer needs to send a large historical backfill or very large numbers of aggregate rows per run, coordinate a bulk import plan instead of increasing request concurrency.
Direct API event discovery
Use Direct API for events that are better known by the backend than the browser:
- Completed billing or subscription state changes.
- Checkout completed, invoice paid, payment failed, refund, downgrade completed, cancel completed.
- Durable setup milestones such as integration configured, OAuth connected, destination configured, API key created, or first event ingested.
- Metered usage, credits consumed, quota calculations, API calls, background jobs, sync results, and anything billable.
first_*milestones that must be race-safe and based on persisted state.
Use the Web SDK instead for browser-only intent before backend state changes, visible prompts, page-level UI friction, and supplemental adoption signals.
When using an AI coding tool, ask it to classify event candidates as:
| Classification | Meaning |
|---|---|
direct_api_now | Durable backend event that should be sent through Direct API now |
existing_source | Already covered by Stripe, PostHog, Segment, Direct API, warehouse, or another reliable event source |
web_sdk_follow_up | Browser-visible intent or UI friction that is better captured by the Web SDK |
Event contract
Every revenue-relevant Direct API event should include:
| Field | Required | Notes |
|---|---|---|
event_name | Yes | Static lower_snake_case name such as usage_limit_hit |
account_id | Yes | Stable customer, account, workspace, tenant, company, or organization ID |
timestamp | Yes | ISO timestamp for when the product event occurred |
event_id | Recommended | Stable idempotency key for retryable completed events |
user_id | Optional | Actor context when a user caused the event |
semantic_type | Recommended | One of Prevenue's revenue event families |
event_kind | Recommended | product_action, billing_event, friction_event, or another normalized kind |
value | Optional | Numeric amount, count, usage value, credit amount, or revenue magnitude |
properties | Optional | Bounded structured context |
Use stable event_id values for completed state changes, for example checkout_completed:<checkout_id> or integration_connected:<provider>:<account_id>.
Do not emit first_* milestones unless persisted state proves first occurrence race-safely. If first-occurrence detection is approximate, skip the event or use a non-first_* name.
Events to add first
Start with a small allowlist of 3 to 6 high-signal backend events.
| Event family | Example names | Direct API guidance |
|---|---|---|
| Activation / first value | account_activated, first_value_completed, onboarding_completed | Emit after persisted state proves completion |
| High-value usage | feature_used, workflow_completed, report_exported, api_credits_used, generation_completed, sync_completed | Best for metered, key-feature, or server-confirmed usage |
| Limit friction | limit_hit, quota_blocked, overage_prompt_viewed | Best when the backend calculates the limit |
| Checkout | checkout_started, checkout_completed, checkout_abandoned | Direct API or Stripe should be authoritative for completed checkout |
| Billing lifecycle | subscription_updated, subscription_upgraded, invoice_payment_failed, payment_succeeded, plan_changed | Prefer Stripe when connected; use Direct API only when your backend is the billing source of truth |
| Downgrade or cancel | downgrade_completed, cancel_completed, subscription_paused | Use bounded reason_code; never send free text |
| Team expansion | team_member_invited, seat_added, role_assigned | Emit after invite or seat persistence succeeds |
| Integration connected | integration_connected, oauth_connected, destination_configured | Emit after credentials/configuration are persisted |
| Product or support friction | sync_failed, setup_failed, support_needed | Use bounded error codes/statuses only |
Sentiment fields
Sentiment is optional and separate from the event family. Missing sentiment is stored as unknown, not neutral; use neutral only when the product signal is explicitly neutral.
Send explicit bounded sentiment from the backend when the source-of-truth system knows it. If sentiment is omitted, Prevenue can infer a reviewable mapping default from the event name, semantic type, event kind, and bounded property keys. Explicit payload sentiment takes precedence over mapped sentiment.
| Field | Values |
|---|---|
sentiment | positive, neutral, negative, unknown |
sentiment_score | Number from -1 to 1 |
sentiment_confidence | Number from 0 to 1 |
sentiment_source | explicit, inferred, ai, integration, unknown |
Do not send free-text feedback, survey responses, chat messages, support transcripts, raw comments, or AI-generated summaries as sentiment context. Send bounded labels and scores only.
See Event KPI Signals for the canonical KPI definitions, evidence bands, and Account filters these fields power.
Data safety
Do not send:
- Passwords, tokens, cookies, signatures, auth headers, API keys, session values, invite links, webhook URLs, provider tokens, or destination secrets.
- Raw PII, full URLs with query strings, full request/response bodies, stack traces, raw logs, prompts, comments, notes, support messages, or descriptions.
- Generic page views, auth page views, modal opens, hovers, local UI toggles, or debug logs as first-batch Direct API events.
Verify setup
Send one account-scoped activation, usage, limit, or setup event from backend code. Then check Settings > Integrations > Direct API and Events in Prevenue.
A successful setup should show:
- The event is accepted by the Direct API route.
- The event normalizes successfully.
- The event attaches to the expected account.
- Rejected payloads clearly explain missing
account_id, auth, or revenue relevance. - No secrets, raw PII, query-string values, or full request/response bodies are stored.