From 5457e37578ecdad20dcf0e6b778766909a530b6c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:21:50 +0000 Subject: [PATCH 01/10] Add hidden guide for building billing tracking with webhooks and audio_duration Co-Authored-By: andrew@assemblyai.com --- fern/docs.yml | 3 + fern/pages/05-guides/billing-tracking.mdx | 408 ++++++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 fern/pages/05-guides/billing-tracking.mdx diff --git a/fern/docs.yml b/fern/docs.yml index ea5568f7..ea96a9ce 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -287,6 +287,9 @@ navigation: hidden: true - page: Webhooks path: pages/05-guides/webhooks.mdx + - page: Build billing tracking for your customers + path: pages/05-guides/billing-tracking.mdx + hidden: true - page: Evaluating STT models path: pages/08-concepts/evals.mdx - page: Account Management diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx new file mode 100644 index 00000000..3767f411 --- /dev/null +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -0,0 +1,408 @@ +--- +title: "Build billing tracking for your customers" +hide-nav-links: true +description: "Learn how to use custom webhook parameters and audio duration to build billing tracking for your customers." +--- + +If you're building a platform that offers transcription services to your own customers, you can use AssemblyAI's webhook parameters and transcript metadata to implement billing tracking. This guide shows you how to associate transcriptions with your customers and track their usage for billing purposes. + +## Overview + +When you submit a transcription request, you can include custom data in the webhook URL that identifies which of your customers the transcription belongs to. When the transcription completes, AssemblyAI sends a webhook notification to your server, and you can then retrieve the full transcript to get the `audio_duration` for billing calculations. + +## Step 1: Include customer identifiers in the webhook URL + +When submitting a transcription, include your customer's identifier as a query parameter in the webhook URL. This allows you to associate the completed transcription with the correct customer. + + + + +```python +import assemblyai as aai + +aai.settings.api_key = "" + +# Include customer_id in the webhook URL +customer_id = "cust_12345" +webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" + +config = aai.TranscriptionConfig().set_webhook(webhook_url) + +transcript = aai.Transcriber().submit("https://example.com/audio.mp3", config) +print(f"Submitted transcript {transcript.id} for customer {customer_id}") +``` + + + + +```javascript +import { AssemblyAI } from "assemblyai"; + +const client = new AssemblyAI({ + apiKey: "", +}); + +// Include customer_id in the webhook URL +const customerId = "cust_12345"; +const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; + +const transcript = await client.transcripts.submit({ + audio: "https://example.com/audio.mp3", + webhook_url: webhookUrl, +}); + +console.log(`Submitted transcript ${transcript.id} for customer ${customerId}`); +``` + + + + +```python +import requests + +base_url = "https://api.assemblyai.com" + +headers = { + "authorization": "", + "content-type": "application/json" +} + +# Include customer_id in the webhook URL +customer_id = "cust_12345" +webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" + +data = { + "audio_url": "https://example.com/audio.mp3", + "webhook_url": webhook_url +} + +response = requests.post(f"{base_url}/v2/transcript", json=data, headers=headers) +transcript = response.json() + +print(f"Submitted transcript {transcript['id']} for customer {customer_id}") +``` + + + + +```javascript +const baseUrl = "https://api.assemblyai.com"; + +const headers = { + authorization: "", + "content-type": "application/json", +}; + +// Include customer_id in the webhook URL +const customerId = "cust_12345"; +const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; + +const data = { + audio_url: "https://example.com/audio.mp3", + webhook_url: webhookUrl, +}; + +const response = await fetch(`${baseUrl}/v2/transcript`, { + method: "POST", + headers: headers, + body: JSON.stringify(data), +}); + +const transcript = await response.json(); +console.log(`Submitted transcript ${transcript.id} for customer ${customerId}`); +``` + + + + +You can include multiple parameters in the webhook URL to track additional metadata: + +``` +https://your-server.com/webhooks/transcription?customer_id=cust_12345&project_id=proj_789&tier=premium +``` + +## Step 2: Secure your webhook with authentication headers + +To ensure webhook requests are genuinely from AssemblyAI, use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters: + + + + +```python +import assemblyai as aai + +aai.settings.api_key = "" + +customer_id = "cust_12345" +webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" + +# Add authentication header +config = aai.TranscriptionConfig().set_webhook( + webhook_url, + "X-Webhook-Secret", + "your-secret-value" +) + +transcript = aai.Transcriber().submit("https://example.com/audio.mp3", config) +``` + + + + +```javascript +import { AssemblyAI } from "assemblyai"; + +const client = new AssemblyAI({ + apiKey: "", +}); + +const customerId = "cust_12345"; +const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; + +const transcript = await client.transcripts.submit({ + audio: "https://example.com/audio.mp3", + webhook_url: webhookUrl, + webhook_auth_header_name: "X-Webhook-Secret", + webhook_auth_header_value: "your-secret-value", +}); +``` + + + + +## Step 3: Handle the webhook and retrieve audio duration + +When the transcription completes, AssemblyAI sends a POST request to your webhook URL with the transcript ID and status. You can then retrieve the full transcript to get the `audio_duration` field. + +### Webhook payload + +The webhook payload contains: + +```json +{ + "transcript_id": "abc123", + "status": "completed" +} +``` + +### Example webhook handler + + + + +```python +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) + +ASSEMBLYAI_API_KEY = "" +WEBHOOK_SECRET = "your-secret-value" + +@app.route("/webhooks/transcription", methods=["POST"]) +def handle_webhook(): + # Verify the webhook secret + if request.headers.get("X-Webhook-Secret") != WEBHOOK_SECRET: + return jsonify({"error": "Unauthorized"}), 401 + + # Get customer_id from query parameters + customer_id = request.args.get("customer_id") + + # Get transcript details from webhook payload + payload = request.json + transcript_id = payload["transcript_id"] + status = payload["status"] + + if status == "completed": + # Retrieve the full transcript to get audio_duration + headers = {"authorization": ASSEMBLYAI_API_KEY} + response = requests.get( + f"https://api.assemblyai.com/v2/transcript/{transcript_id}", + headers=headers + ) + transcript = response.json() + + # audio_duration is in seconds + audio_duration_seconds = transcript["audio_duration"] + + # Convert to hours for billing (AssemblyAI charges per hour) + audio_duration_hours = audio_duration_seconds / 3600 + + # Record usage for billing + record_usage( + customer_id=customer_id, + transcript_id=transcript_id, + duration_seconds=audio_duration_seconds, + duration_hours=audio_duration_hours + ) + + print(f"Customer {customer_id}: {audio_duration_seconds}s ({audio_duration_hours:.4f} hours)") + + return jsonify({"status": "ok"}), 200 + +def record_usage(customer_id, transcript_id, duration_seconds, duration_hours): + # Implement your billing logic here + # For example, store in a database: + # - customer_id + # - transcript_id + # - duration_seconds + # - duration_hours + # - timestamp + pass + +if __name__ == "__main__": + app.run(port=5000) +``` + + + + +```javascript +const express = require("express"); +const app = express(); + +app.use(express.json()); + +const ASSEMBLYAI_API_KEY = ""; +const WEBHOOK_SECRET = "your-secret-value"; + +app.post("/webhooks/transcription", async (req, res) => { + // Verify the webhook secret + if (req.headers["x-webhook-secret"] !== WEBHOOK_SECRET) { + return res.status(401).json({ error: "Unauthorized" }); + } + + // Get customer_id from query parameters + const customerId = req.query.customer_id; + + // Get transcript details from webhook payload + const { transcript_id, status } = req.body; + + if (status === "completed") { + // Retrieve the full transcript to get audio_duration + const response = await fetch( + `https://api.assemblyai.com/v2/transcript/${transcript_id}`, + { + headers: { authorization: ASSEMBLYAI_API_KEY }, + } + ); + const transcript = await response.json(); + + // audio_duration is in seconds + const audioDurationSeconds = transcript.audio_duration; + + // Convert to hours for billing (AssemblyAI charges per hour) + const audioDurationHours = audioDurationSeconds / 3600; + + // Record usage for billing + await recordUsage({ + customerId, + transcriptId: transcript_id, + durationSeconds: audioDurationSeconds, + durationHours: audioDurationHours, + }); + + console.log( + `Customer ${customerId}: ${audioDurationSeconds}s (${audioDurationHours.toFixed(4)} hours)` + ); + } + + res.json({ status: "ok" }); +}); + +async function recordUsage({ customerId, transcriptId, durationSeconds, durationHours }) { + // Implement your billing logic here + // For example, store in a database: + // - customerId + // - transcriptId + // - durationSeconds + // - durationHours + // - timestamp +} + +app.listen(5000, () => { + console.log("Webhook server running on port 5000"); +}); +``` + + + + +## The audio_duration field + +The `audio_duration` field in the transcript response represents the duration of the audio file in seconds. This is the value you should use for billing calculations. + +Example transcript response (relevant fields): + +```json +{ + "id": "abc123", + "status": "completed", + "audio_duration": 3725, + "text": "...", + ... +} +``` + +In this example, `audio_duration` is 3725 seconds, which equals approximately 1.035 hours (3725 / 3600). + +## Billing calculation example + +Here's how you might calculate billing based on audio duration: + +```python +def calculate_billing(audio_duration_seconds, rate_per_hour): + """ + Calculate billing based on audio duration. + + Args: + audio_duration_seconds: Duration from the transcript's audio_duration field + rate_per_hour: Your rate per hour of audio (e.g., $0.50) + + Returns: + The amount to bill + """ + hours = audio_duration_seconds / 3600 + return hours * rate_per_hour + +# Example usage +audio_duration = 3725 # seconds +rate = 0.50 # $0.50 per hour + +amount = calculate_billing(audio_duration, rate) +print(f"Bill amount: ${amount:.4f}") # Bill amount: $0.5174 +``` + +## Best practices + +When implementing billing tracking, consider the following best practices: + +1. **Store the transcript ID**: Always store the `transcript_id` alongside usage records. This allows you to audit and verify billing data by retrieving the original transcript. + +2. **Handle errors gracefully**: If a transcription fails (`status: "error"`), don't bill the customer for that request. You may want to log failed transcriptions for debugging. + +3. **Use idempotent webhook handlers**: Webhooks may be delivered more than once. Use the `transcript_id` to ensure you don't double-count usage. + +4. **Secure your webhooks**: Always use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI. + +5. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports. + +6. **Round appropriately**: Decide on a rounding strategy for billing (e.g., round up to the nearest second, minute, or hour) and apply it consistently. + +## Additional metadata you can track + +Beyond `audio_duration`, you may want to track other fields from the transcript response for detailed billing or analytics: + +| Field | Description | +|-------|-------------| +| `audio_duration` | Duration of the audio in seconds | +| `speech_model` | The model used (e.g., "universal", "slam-1") - different models may have different pricing | +| `language_code` | The detected or specified language | +| `speaker_labels` | Whether speaker diarization was enabled | +| `auto_highlights` | Whether auto highlights was enabled | + +## Next steps + +- Learn more about [webhooks](/docs/guides/webhooks) and their configuration options +- Explore the [API reference](/docs/api-reference) for the complete transcript response schema +- Review [pricing](https://www.assemblyai.com/pricing) to understand AssemblyAI's billing model From e9ebb476d67ba49ea8b93d09f34c7537f0714299 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:08:29 +0000 Subject: [PATCH 02/10] Update billing tracking guide with streaming section and improved examples Co-Authored-By: andrew@assemblyai.com --- fern/docs.yml | 2 +- fern/pages/05-guides/billing-tracking.mdx | 440 ++++++++++------------ 2 files changed, 199 insertions(+), 243 deletions(-) diff --git a/fern/docs.yml b/fern/docs.yml index ea96a9ce..1f497f40 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -287,7 +287,7 @@ navigation: hidden: true - page: Webhooks path: pages/05-guides/webhooks.mdx - - page: Build billing tracking for your customers + - page: Tracking customer transcription usage path: pages/05-guides/billing-tracking.mdx hidden: true - page: Evaluating STT models diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index 3767f411..a1750d7f 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -1,18 +1,18 @@ --- -title: "Build billing tracking for your customers" +title: "Tracking customer transcription usage" hide-nav-links: true -description: "Learn how to use custom webhook parameters and audio duration to build billing tracking for your customers." +description: "Learn how to track individual customer usage within your product using AssemblyAI's webhook system and metadata." --- -If you're building a platform that offers transcription services to your own customers, you can use AssemblyAI's webhook parameters and transcript metadata to implement billing tracking. This guide shows you how to associate transcriptions with your customers and track their usage for billing purposes. +This guide explains how to track individual customer usage within your product using AssemblyAI's webhook system and metadata for async transcription, and session events for streaming transcription. -## Overview +## Async transcription usage tracking -When you submit a transcription request, you can include custom data in the webhook URL that identifies which of your customers the transcription belongs to. When the transcription completes, AssemblyAI sends a webhook notification to your server, and you can then retrieve the full transcript to get the `audio_duration` for billing calculations. +By combining webhooks with custom metadata, you can track audio duration per customer and monitor their usage of your transcription service. -## Step 1: Include customer identifiers in the webhook URL +### Step 1: Set up webhooks with customer metadata -When submitting a transcription, include your customer's identifier as a query parameter in the webhook URL. This allows you to associate the completed transcription with the correct customer. +When submitting a transcription request, include your webhook URL with the customer ID as a query parameter. This allows you to associate each transcription with a specific customer. @@ -22,14 +22,15 @@ import assemblyai as aai aai.settings.api_key = "" -# Include customer_id in the webhook URL -customer_id = "cust_12345" -webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" +# Add customer_id as a query parameter to your webhook URL +webhook_url = "https://your-domain.com/webhook?customer_id=customer_123" -config = aai.TranscriptionConfig().set_webhook(webhook_url) +config = aai.TranscriptionConfig( + speech_model=aai.SpeechModel.best +).set_webhook(webhook_url) -transcript = aai.Transcriber().submit("https://example.com/audio.mp3", config) -print(f"Submitted transcript {transcript.id} for customer {customer_id}") +# Submit without waiting for completion +aai.Transcriber().submit("https://example.com/audio.mp3", config) ``` @@ -42,88 +43,43 @@ const client = new AssemblyAI({ apiKey: "", }); -// Include customer_id in the webhook URL -const customerId = "cust_12345"; -const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; +// Add customer_id as a query parameter to your webhook URL +const webhookUrl = "https://your-domain.com/webhook?customer_id=customer_123"; const transcript = await client.transcripts.submit({ audio: "https://example.com/audio.mp3", + speech_model: "best", webhook_url: webhookUrl, }); - -console.log(`Submitted transcript ${transcript.id} for customer ${customerId}`); ``` - - -```python -import requests - -base_url = "https://api.assemblyai.com" - -headers = { - "authorization": "", - "content-type": "application/json" -} - -# Include customer_id in the webhook URL -customer_id = "cust_12345" -webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" - -data = { - "audio_url": "https://example.com/audio.mp3", - "webhook_url": webhook_url -} + -response = requests.post(f"{base_url}/v2/transcript", json=data, headers=headers) -transcript = response.json() +You can add multiple query parameters to track additional information: -print(f"Submitted transcript {transcript['id']} for customer {customer_id}") +``` +https://your-domain.com/webhook?customer_id=123&project_id=456&order_id=789 ``` - - - -```javascript -const baseUrl = "https://api.assemblyai.com"; - -const headers = { - authorization: "", - "content-type": "application/json", -}; - -// Include customer_id in the webhook URL -const customerId = "cust_12345"; -const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; +This allows you to track usage across multiple dimensions (customer, project, order, etc.). -const data = { - audio_url: "https://example.com/audio.mp3", - webhook_url: webhookUrl, -}; +### Step 2: Handle the webhook delivery -const response = await fetch(`${baseUrl}/v2/transcript`, { - method: "POST", - headers: headers, - body: JSON.stringify(data), -}); +When the transcription completes, AssemblyAI sends a POST request to your webhook URL with the following payload: -const transcript = await response.json(); -console.log(`Submitted transcript ${transcript.id} for customer ${customerId}`); +```json +{ + "transcript_id": "5552493-16d8-42d8-8feb-c2a16b56f6e8", + "status": "completed" +} ``` - - - -You can include multiple parameters in the webhook URL to track additional metadata: - -``` -https://your-server.com/webhooks/transcription?customer_id=cust_12345&project_id=proj_789&tier=premium -``` +Extract both the `transcript_id` from the payload and the `customer_id` from your URL query parameters. -## Step 2: Secure your webhook with authentication headers +### Step 3: Retrieve the transcript with audio duration -To ensure webhook requests are genuinely from AssemblyAI, use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters: +Use the transcript ID to fetch the complete transcript details, which includes the `audio_duration` field (in seconds). @@ -133,17 +89,12 @@ import assemblyai as aai aai.settings.api_key = "" -customer_id = "cust_12345" -webhook_url = f"https://your-server.com/webhooks/transcription?customer_id={customer_id}" - -# Add authentication header -config = aai.TranscriptionConfig().set_webhook( - webhook_url, - "X-Webhook-Secret", - "your-secret-value" -) +# Get transcript using the ID from webhook +transcript = aai.Transcript.get_by_id("") -transcript = aai.Transcriber().submit("https://example.com/audio.mp3", config) +if transcript.status == aai.TranscriptStatus.completed: + audio_duration = transcript.audio_duration # Duration in seconds + # Use audio_duration for billing/tracking ``` @@ -156,99 +107,55 @@ const client = new AssemblyAI({ apiKey: "", }); -const customerId = "cust_12345"; -const webhookUrl = `https://your-server.com/webhooks/transcription?customer_id=${customerId}`; +// Get transcript using the ID from webhook +const transcript = await client.transcripts.get(""); -const transcript = await client.transcripts.submit({ - audio: "https://example.com/audio.mp3", - webhook_url: webhookUrl, - webhook_auth_header_name: "X-Webhook-Secret", - webhook_auth_header_value: "your-secret-value", -}); +if (transcript.status === "completed") { + const audioDuration = transcript.audio_duration; // Duration in seconds + // Use audioDuration for billing/tracking +} ``` -## Step 3: Handle the webhook and retrieve audio duration +### Step 4: Track usage per customer -When the transcription completes, AssemblyAI sends a POST request to your webhook URL with the transcript ID and status. You can then retrieve the full transcript to get the `audio_duration` field. - -### Webhook payload - -The webhook payload contains: - -```json -{ - "transcript_id": "abc123", - "status": "completed" -} -``` - -### Example webhook handler +Combine the customer ID from your webhook URL with the audio duration from the transcript: ```python from flask import Flask, request, jsonify -import requests +import assemblyai as aai app = Flask(__name__) -ASSEMBLYAI_API_KEY = "" -WEBHOOK_SECRET = "your-secret-value" +aai.settings.api_key = "" -@app.route("/webhooks/transcription", methods=["POST"]) +@app.route("/webhook", methods=["POST"]) def handle_webhook(): - # Verify the webhook secret - if request.headers.get("X-Webhook-Secret") != WEBHOOK_SECRET: - return jsonify({"error": "Unauthorized"}), 401 - - # Get customer_id from query parameters + # Extract customer_id from query parameters customer_id = request.args.get("customer_id") - # Get transcript details from webhook payload - payload = request.json - transcript_id = payload["transcript_id"] - status = payload["status"] - - if status == "completed": - # Retrieve the full transcript to get audio_duration - headers = {"authorization": ASSEMBLYAI_API_KEY} - response = requests.get( - f"https://api.assemblyai.com/v2/transcript/{transcript_id}", - headers=headers - ) - transcript = response.json() - - # audio_duration is in seconds - audio_duration_seconds = transcript["audio_duration"] + # Extract transcript_id from webhook payload + data = request.get_json() + transcript_id = data["transcript_id"] - # Convert to hours for billing (AssemblyAI charges per hour) - audio_duration_hours = audio_duration_seconds / 3600 + if data["status"] == "completed": + # Fetch transcript details + transcript = aai.Transcript.get_by_id(transcript_id) + audio_duration = transcript.audio_duration - # Record usage for billing - record_usage( - customer_id=customer_id, - transcript_id=transcript_id, - duration_seconds=audio_duration_seconds, - duration_hours=audio_duration_hours - ) - - print(f"Customer {customer_id}: {audio_duration_seconds}s ({audio_duration_hours:.4f} hours)") + # Log usage for this customer + log_customer_usage(customer_id, audio_duration, transcript_id) return jsonify({"status": "ok"}), 200 -def record_usage(customer_id, transcript_id, duration_seconds, duration_hours): +def log_customer_usage(customer_id, audio_duration, transcript_id): # Implement your billing logic here - # For example, store in a database: - # - customer_id - # - transcript_id - # - duration_seconds - # - duration_hours - # - timestamp - pass + print(f"Customer {customer_id}: {audio_duration}s for transcript {transcript_id}") if __name__ == "__main__": app.run(port=5000) @@ -259,65 +166,37 @@ if __name__ == "__main__": ```javascript const express = require("express"); -const app = express(); +const { AssemblyAI } = require("assemblyai"); +const app = express(); app.use(express.json()); -const ASSEMBLYAI_API_KEY = ""; -const WEBHOOK_SECRET = "your-secret-value"; - -app.post("/webhooks/transcription", async (req, res) => { - // Verify the webhook secret - if (req.headers["x-webhook-secret"] !== WEBHOOK_SECRET) { - return res.status(401).json({ error: "Unauthorized" }); - } +const client = new AssemblyAI({ + apiKey: "", +}); - // Get customer_id from query parameters +app.post("/webhook", async (req, res) => { + // Extract customer_id from query parameters const customerId = req.query.customer_id; - // Get transcript details from webhook payload + // Extract transcript_id from webhook payload const { transcript_id, status } = req.body; if (status === "completed") { - // Retrieve the full transcript to get audio_duration - const response = await fetch( - `https://api.assemblyai.com/v2/transcript/${transcript_id}`, - { - headers: { authorization: ASSEMBLYAI_API_KEY }, - } - ); - const transcript = await response.json(); - - // audio_duration is in seconds - const audioDurationSeconds = transcript.audio_duration; - - // Convert to hours for billing (AssemblyAI charges per hour) - const audioDurationHours = audioDurationSeconds / 3600; - - // Record usage for billing - await recordUsage({ - customerId, - transcriptId: transcript_id, - durationSeconds: audioDurationSeconds, - durationHours: audioDurationHours, - }); - - console.log( - `Customer ${customerId}: ${audioDurationSeconds}s (${audioDurationHours.toFixed(4)} hours)` - ); + // Fetch transcript details + const transcript = await client.transcripts.get(transcript_id); + const audioDuration = transcript.audio_duration; + + // Log usage for this customer + logCustomerUsage(customerId, audioDuration, transcript_id); } res.json({ status: "ok" }); }); -async function recordUsage({ customerId, transcriptId, durationSeconds, durationHours }) { +function logCustomerUsage(customerId, audioDuration, transcriptId) { // Implement your billing logic here - // For example, store in a database: - // - customerId - // - transcriptId - // - durationSeconds - // - durationHours - // - timestamp + console.log(`Customer ${customerId}: ${audioDuration}s for transcript ${transcriptId}`); } app.listen(5000, () => { @@ -328,81 +207,158 @@ app.listen(5000, () => { -## The audio_duration field +### Key fields for usage tracking -The `audio_duration` field in the transcript response represents the duration of the audio file in seconds. This is the value you should use for billing calculations. +From the transcript response, you'll have access to: -Example transcript response (relevant fields): +| Field | Description | +|-------|-------------| +| `audio_duration` | Duration in seconds of the transcribed media file | +| `id` | The unique transcript identifier | +| Customer ID | From your webhook URL query parameters | -```json -{ - "id": "abc123", - "status": "completed", - "audio_duration": 3725, - "text": "...", - ... +## Streaming transcription usage tracking + +Unlike async transcription which uses webhooks, streaming transcription requires a different approach. You'll track usage by managing customer IDs in your own application state/session management, capturing the `audio_duration_seconds` from the Termination event, and associating the duration with the customer ID for billing/tracking. + +### Step 1: Set up your WebSocket connection + +Connect to AssemblyAI's streaming service. The customer ID is managed entirely in your application and is never sent to AssemblyAI. + +```python +import websocket +import json +from urllib.parse import urlencode +from datetime import datetime + +# Configuration +YOUR_API_KEY = "" + +CONNECTION_PARAMS = { + "sample_rate": 16000, + "format_turns": True, } + +API_ENDPOINT = f"wss://streaming.assemblyai.com/v3/ws?{urlencode(CONNECTION_PARAMS)}" ``` -In this example, `audio_duration` is 3725 seconds, which equals approximately 1.035 hours (3725 / 3600). +### Step 2: Capture audio duration from the Termination event + +The key to tracking usage is capturing the `audio_duration_seconds` field from the Termination message. This is sent when the streaming session ends. + +```python +def on_message(ws, message): + """Handle WebSocket messages""" + try: + data = json.loads(message) + msg_type = data.get("type") + + if msg_type == "Begin": + session_id = data.get("id") + print(f"Session started: {session_id}") + + elif msg_type == "Turn": + transcript = data.get("transcript", "") + if data.get("turn_is_formatted"): + print(f"Transcript: {transcript}") + + elif msg_type == "Termination": + # Extract audio duration - this is what you need for billing + audio_duration_seconds = data.get("audio_duration_seconds", 0) + session_duration_seconds = data.get("session_duration_seconds", 0) + + print(f"\nSession terminated:") + print(f" Audio Duration: {audio_duration_seconds} seconds") + print(f" Session Duration: {session_duration_seconds} seconds") + + # Here you would associate audio_duration_seconds with your customer + # using whatever session management system you have in place + customer_id = get_customer_id_from_session() # Your implementation + log_customer_usage(customer_id, session_duration_seconds) + + except json.JSONDecodeError as e: + print(f"Error decoding message: {e}") + except Exception as e: + print(f"Error handling message: {e}") +``` -## Billing calculation example +### Step 3: Log customer usage -Here's how you might calculate billing based on audio duration: +Create a function to store the session duration for billing/tracking. You'll need to implement your own session management to associate the WebSocket connection with a customer ID. ```python -def calculate_billing(audio_duration_seconds, rate_per_hour): +def log_customer_usage(customer_id, session_duration_seconds): """ - Calculate billing based on audio duration. + Log streaming usage for a customer. Args: - audio_duration_seconds: Duration from the transcript's audio_duration field - rate_per_hour: Your rate per hour of audio (e.g., $0.50) - - Returns: - The amount to bill + customer_id: Your customer's unique identifier (from your session) + session_duration_seconds: Duration of the session (from Termination event) """ - hours = audio_duration_seconds / 3600 - return hours * rate_per_hour + usage_record = { + "customer_id": customer_id, + "session_duration_seconds": session_duration_seconds, + "session_duration_minutes": session_duration_seconds / 60.0, + "timestamp": datetime.utcnow().isoformat(), + "service": "streaming_transcription" + } + + # Store in your database + # db.usage_logs.insert_one(usage_record) + + # Or write to a log file + with open("streaming_usage.jsonl", "a") as f: + f.write(f"{json.dumps(usage_record)}\n") + + print(f"Logged {session_duration_seconds}s of streaming for customer {customer_id}") +``` + +### Session duration vs audio duration + +From the Termination event, you receive two fields: + +| Field | Description | +|-------|-------------| +| `session_duration_seconds` | Total time the session was open | +| `audio_duration_seconds` | Total seconds of audio actually processed | + + +Streaming transcription is billed based on `session_duration_seconds`, not `audio_duration_seconds`. Make sure you track the correct metric for accurate billing. + + +### Session management -# Example usage -audio_duration = 3725 # seconds -rate = 0.50 # $0.50 per hour +You need to implement your own session management to associate WebSocket connections with customer IDs. This could be through user authentication tokens, session cookies, database lookups, or in-memory session stores. Track the customer ID throughout the WebSocket lifecycle so you can associate it with the session duration when the Termination event arrives. -amount = calculate_billing(audio_duration, rate) -print(f"Bill amount: ${amount:.4f}") # Bill amount: $0.5174 +### Proper session termination + +Always close sessions properly to ensure you receive the Termination event and avoid unexpected costs: + +```python +# Send termination message when done +terminate_message = {"type": "Terminate"} +ws.send(json.dumps(terminate_message)) ``` ## Best practices When implementing billing tracking, consider the following best practices: -1. **Store the transcript ID**: Always store the `transcript_id` alongside usage records. This allows you to audit and verify billing data by retrieving the original transcript. +1. **Store the transcript/session ID**: Always store the identifier alongside usage records. This allows you to audit and verify billing data. 2. **Handle errors gracefully**: If a transcription fails (`status: "error"`), don't bill the customer for that request. You may want to log failed transcriptions for debugging. -3. **Use idempotent webhook handlers**: Webhooks may be delivered more than once. Use the `transcript_id` to ensure you don't double-count usage. +3. **Use idempotent handlers**: Webhooks may be delivered more than once. Use the `transcript_id` to ensure you don't double-count usage. -4. **Secure your webhooks**: Always use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI. +4. **Secure your webhooks**: Use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI. 5. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports. 6. **Round appropriately**: Decide on a rounding strategy for billing (e.g., round up to the nearest second, minute, or hour) and apply it consistently. -## Additional metadata you can track - -Beyond `audio_duration`, you may want to track other fields from the transcript response for detailed billing or analytics: - -| Field | Description | -|-------|-------------| -| `audio_duration` | Duration of the audio in seconds | -| `speech_model` | The model used (e.g., "universal", "slam-1") - different models may have different pricing | -| `language_code` | The detected or specified language | -| `speaker_labels` | Whether speaker diarization was enabled | -| `auto_highlights` | Whether auto highlights was enabled | - ## Next steps -- Learn more about [webhooks](/docs/guides/webhooks) and their configuration options -- Explore the [API reference](/docs/api-reference) for the complete transcript response schema -- Review [pricing](https://www.assemblyai.com/pricing) to understand AssemblyAI's billing model +- Learn more about [webhooks](/docs/deployment/webhooks) and their configuration options +- Explore the [Submit Transcript API](/docs/api-reference/transcripts/submit) for async transcription +- Explore the [Get Transcript API](/docs/api-reference/transcripts/get) for retrieving transcript details +- Review the [Streaming API](/docs/api-reference/streaming) for real-time transcription From db4250948d2693c52d15ca2d63b48db06aa26e2b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:14:28 +0000 Subject: [PATCH 03/10] Add custom slug tracking-your-customers-usage Co-Authored-By: andrew@assemblyai.com --- fern/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/fern/docs.yml b/fern/docs.yml index 1f497f40..fbc1c4b6 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -289,6 +289,7 @@ navigation: path: pages/05-guides/webhooks.mdx - page: Tracking customer transcription usage path: pages/05-guides/billing-tracking.mdx + slug: tracking-your-customers-usage hidden: true - page: Evaluating STT models path: pages/08-concepts/evals.mdx From 25a13ea87ab3bee956b3c15112b28c01b0308352 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:35:56 +0000 Subject: [PATCH 04/10] Move billing tracking doc to new Platform section in cookbooks Co-Authored-By: andrew@assemblyai.com --- fern/docs.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fern/docs.yml b/fern/docs.yml index fbc1c4b6..73f5369e 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -287,10 +287,6 @@ navigation: hidden: true - page: Webhooks path: pages/05-guides/webhooks.mdx - - page: Tracking customer transcription usage - path: pages/05-guides/billing-tracking.mdx - slug: tracking-your-customers-usage - hidden: true - page: Evaluating STT models path: pages/08-concepts/evals.mdx - page: Account Management @@ -824,6 +820,13 @@ navigation: - link: Speechmatics to AssemblyAI href: /docs/guides/speechmatics_to_aai_streaming + - section: Platform + skip-slug: true + contents: + - page: Tracking customer transcription usage + path: pages/05-guides/billing-tracking.mdx + slug: tracking-your-customers-usage + # Legacy guides - page: Process Speaker Labels with LeMURs Custom Text Input Parameter path: pages/05-guides/cookbooks/lemur/input-text-speaker-labels.mdx From bc2f89ae8953ce62e97910a7d2362e0ef41c8098 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:44:21 +0000 Subject: [PATCH 05/10] Simplify Step 4 - replace Flask/Express code with explanation Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 89 ++--------------------- 1 file changed, 6 insertions(+), 83 deletions(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index a1750d7f..7075218b 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -121,91 +121,14 @@ if (transcript.status === "completed") { ### Step 4: Track usage per customer -Combine the customer ID from your webhook URL with the audio duration from the transcript: +In your webhook handler, combine the customer ID from your webhook URL query parameters with the audio duration from the transcript to record usage: - - - -```python -from flask import Flask, request, jsonify -import assemblyai as aai - -app = Flask(__name__) - -aai.settings.api_key = "" - -@app.route("/webhook", methods=["POST"]) -def handle_webhook(): - # Extract customer_id from query parameters - customer_id = request.args.get("customer_id") - - # Extract transcript_id from webhook payload - data = request.get_json() - transcript_id = data["transcript_id"] - - if data["status"] == "completed": - # Fetch transcript details - transcript = aai.Transcript.get_by_id(transcript_id) - audio_duration = transcript.audio_duration - - # Log usage for this customer - log_customer_usage(customer_id, audio_duration, transcript_id) - - return jsonify({"status": "ok"}), 200 - -def log_customer_usage(customer_id, audio_duration, transcript_id): - # Implement your billing logic here - print(f"Customer {customer_id}: {audio_duration}s for transcript {transcript_id}") - -if __name__ == "__main__": - app.run(port=5000) -``` - - - - -```javascript -const express = require("express"); -const { AssemblyAI } = require("assemblyai"); +1. Extract the `customer_id` from the webhook URL query parameters +2. Extract the `transcript_id` from the webhook payload +3. If the status is `completed`, fetch the transcript using the SDK to get the `audio_duration` +4. Store the usage record in your database with the customer ID, transcript ID, audio duration, and timestamp -const app = express(); -app.use(express.json()); - -const client = new AssemblyAI({ - apiKey: "", -}); - -app.post("/webhook", async (req, res) => { - // Extract customer_id from query parameters - const customerId = req.query.customer_id; - - // Extract transcript_id from webhook payload - const { transcript_id, status } = req.body; - - if (status === "completed") { - // Fetch transcript details - const transcript = await client.transcripts.get(transcript_id); - const audioDuration = transcript.audio_duration; - - // Log usage for this customer - logCustomerUsage(customerId, audioDuration, transcript_id); - } - - res.json({ status: "ok" }); -}); - -function logCustomerUsage(customerId, audioDuration, transcriptId) { - // Implement your billing logic here - console.log(`Customer ${customerId}: ${audioDuration}s for transcript ${transcriptId}`); -} - -app.listen(5000, () => { - console.log("Webhook server running on port 5000"); -}); -``` - - - +This allows you to aggregate usage per customer for billing purposes. ### Key fields for usage tracking From 8d9ae8c9110bec3341220ec434f99c87fe2cdf15 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:47:25 +0000 Subject: [PATCH 06/10] Add intro for two methods, simplify streaming Step 3, emphasize session_duration_seconds Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 43 ++++++++--------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index 7075218b..bc1dce08 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -4,7 +4,13 @@ hide-nav-links: true description: "Learn how to track individual customer usage within your product using AssemblyAI's webhook system and metadata." --- -This guide explains how to track individual customer usage within your product using AssemblyAI's webhook system and metadata for async transcription, and session events for streaming transcription. +This guide explains how to track individual customer usage within your product for billing purposes. There are two separate methods depending on which transcription approach you use: + +1. **Async transcription**: Use webhooks with custom query parameters to associate transcriptions with customers, then retrieve the `audio_duration` from the transcript response. + +2. **Streaming transcription**: Manage customer IDs in your application state and capture the `session_duration_seconds` from the WebSocket Termination event. + +This guide covers both methods in detail. ## Async transcription usage tracking @@ -142,7 +148,7 @@ From the transcript response, you'll have access to: ## Streaming transcription usage tracking -Unlike async transcription which uses webhooks, streaming transcription requires a different approach. You'll track usage by managing customer IDs in your own application state/session management, capturing the `audio_duration_seconds` from the Termination event, and associating the duration with the customer ID for billing/tracking. +Unlike async transcription which uses webhooks, streaming transcription requires a different approach. You'll track usage by managing customer IDs in your own application state/session management, capturing the `session_duration_seconds` from the Termination event, and associating the duration with the customer ID for billing/tracking. AssemblyAI bills streaming based on session duration, so this is the metric you should track. ### Step 1: Set up your WebSocket connection @@ -207,34 +213,13 @@ def on_message(ws, message): ### Step 3: Log customer usage -Create a function to store the session duration for billing/tracking. You'll need to implement your own session management to associate the WebSocket connection with a customer ID. +When you receive the Termination event, store the session duration for billing/tracking: -```python -def log_customer_usage(customer_id, session_duration_seconds): - """ - Log streaming usage for a customer. - - Args: - customer_id: Your customer's unique identifier (from your session) - session_duration_seconds: Duration of the session (from Termination event) - """ - usage_record = { - "customer_id": customer_id, - "session_duration_seconds": session_duration_seconds, - "session_duration_minutes": session_duration_seconds / 60.0, - "timestamp": datetime.utcnow().isoformat(), - "service": "streaming_transcription" - } - - # Store in your database - # db.usage_logs.insert_one(usage_record) - - # Or write to a log file - with open("streaming_usage.jsonl", "a") as f: - f.write(f"{json.dumps(usage_record)}\n") - - print(f"Logged {session_duration_seconds}s of streaming for customer {customer_id}") -``` +1. Retrieve the customer ID from your session management system (authentication tokens, session cookies, etc.) +2. Extract the `session_duration_seconds` from the Termination event +3. Store the usage record in your database with the customer ID, session duration, and timestamp + +Since AssemblyAI bills streaming based on `session_duration_seconds`, this is the metric you should track for accurate billing. ### Session duration vs audio duration From 59051e35f893b4192240c570d6abf11955efb85c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:55:17 +0000 Subject: [PATCH 07/10] Add raw HTTP code examples and note about recommended strategy Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 89 ++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index bc1dce08..0f68dbdd 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -4,7 +4,13 @@ hide-nav-links: true description: "Learn how to track individual customer usage within your product using AssemblyAI's webhook system and metadata." --- -This guide explains how to track individual customer usage within your product for billing purposes. There are two separate methods depending on which transcription approach you use: +This guide explains how to track individual customer usage within your product for billing purposes. + + +This is the recommended approach for tracking customer usage. Creating separate API keys for each of your customers is not an optimal strategy for usage tracking, as it adds unnecessary complexity and makes it harder to manage your account. + + +There are two separate methods depending on which transcription approach you use: 1. **Async transcription**: Use webhooks with custom query parameters to associate transcriptions with customers, then retrieve the `audio_duration` from the transcript response. @@ -39,6 +45,28 @@ config = aai.TranscriptionConfig( aai.Transcriber().submit("https://example.com/audio.mp3", config) ``` + + + +```python +import requests + +base_url = "https://api.assemblyai.com" +headers = {"authorization": ""} + +# Add customer_id as a query parameter to your webhook URL +webhook_url = "https://your-domain.com/webhook?customer_id=customer_123" + +data = { + "audio_url": "https://example.com/audio.mp3", + "webhook_url": webhook_url +} + +# Submit without waiting for completion +response = requests.post(base_url + "/v2/transcript", headers=headers, json=data) +transcript_id = response.json()["id"] +``` + @@ -59,6 +87,28 @@ const transcript = await client.transcripts.submit({ }); ``` + + + +```javascript +import axios from "axios"; + +const baseUrl = "https://api.assemblyai.com"; +const headers = { authorization: "" }; + +// Add customer_id as a query parameter to your webhook URL +const webhookUrl = "https://your-domain.com/webhook?customer_id=customer_123"; + +const data = { + audio_url: "https://example.com/audio.mp3", + webhook_url: webhookUrl, +}; + +// Submit without waiting for completion +const response = await axios.post(`${baseUrl}/v2/transcript`, data, { headers }); +const transcriptId = response.data.id; +``` + @@ -103,6 +153,24 @@ if transcript.status == aai.TranscriptStatus.completed: # Use audio_duration for billing/tracking ``` + + + +```python +import requests + +base_url = "https://api.assemblyai.com" +headers = {"authorization": ""} + +# Get transcript using the ID from webhook +response = requests.get(base_url + "/v2/transcript/", headers=headers) +transcript = response.json() + +if transcript["status"] == "completed": + audio_duration = transcript["audio_duration"] # Duration in seconds + # Use audio_duration for billing/tracking +``` + @@ -122,6 +190,25 @@ if (transcript.status === "completed") { } ``` + + + +```javascript +import axios from "axios"; + +const baseUrl = "https://api.assemblyai.com"; +const headers = { authorization: "" }; + +// Get transcript using the ID from webhook +const response = await axios.get(`${baseUrl}/v2/transcript/`, { headers }); +const transcript = response.data; + +if (transcript.status === "completed") { + const audioDuration = transcript.audio_duration; // Duration in seconds + // Use audioDuration for billing/tracking +} +``` + From f00abb7c5162694609a32fc3ff062d606a69e662 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:59:58 +0000 Subject: [PATCH 08/10] Remove Key fields for usage tracking section from async Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index 0f68dbdd..272e14d7 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -223,16 +223,6 @@ In your webhook handler, combine the customer ID from your webhook URL query par This allows you to aggregate usage per customer for billing purposes. -### Key fields for usage tracking - -From the transcript response, you'll have access to: - -| Field | Description | -|-------|-------------| -| `audio_duration` | Duration in seconds of the transcribed media file | -| `id` | The unique transcript identifier | -| Customer ID | From your webhook URL query parameters | - ## Streaming transcription usage tracking Unlike async transcription which uses webhooks, streaming transcription requires a different approach. You'll track usage by managing customer IDs in your own application state/session management, capturing the `session_duration_seconds` from the Termination event, and associating the duration with the customer ID for billing/tracking. AssemblyAI bills streaming based on session duration, so this is the metric you should track. From 73c6f1d40b767dc1560fcebefed1c10d8f59701d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:03:11 +0000 Subject: [PATCH 09/10] Remove idempotent handlers best practice Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index 272e14d7..924e930f 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -333,13 +333,11 @@ When implementing billing tracking, consider the following best practices: 2. **Handle errors gracefully**: If a transcription fails (`status: "error"`), don't bill the customer for that request. You may want to log failed transcriptions for debugging. -3. **Use idempotent handlers**: Webhooks may be delivered more than once. Use the `transcript_id` to ensure you don't double-count usage. +3. **Secure your webhooks**: Use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI. -4. **Secure your webhooks**: Use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI. +4. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports. -5. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports. - -6. **Round appropriately**: Decide on a rounding strategy for billing (e.g., round up to the nearest second, minute, or hour) and apply it consistently. +5. **Round appropriately**: Decide on a rounding strategy for billing (e.g., round up to the nearest second, minute, or hour) and apply it consistently. ## Next steps From 917a1e0e3d299e01b859dc279827edb2240d368d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:05:28 +0000 Subject: [PATCH 10/10] Remove Round appropriately best practice Co-Authored-By: andrew@assemblyai.com --- fern/pages/05-guides/billing-tracking.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/fern/pages/05-guides/billing-tracking.mdx b/fern/pages/05-guides/billing-tracking.mdx index 924e930f..e93f9ece 100644 --- a/fern/pages/05-guides/billing-tracking.mdx +++ b/fern/pages/05-guides/billing-tracking.mdx @@ -337,8 +337,6 @@ When implementing billing tracking, consider the following best practices: 4. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports. -5. **Round appropriately**: Decide on a rounding strategy for billing (e.g., round up to the nearest second, minute, or hour) and apply it consistently. - ## Next steps - Learn more about [webhooks](/docs/deployment/webhooks) and their configuration options