Skip to content

Commit 0549a1f

Browse files
authored
Merge pull request #611 from AssemblyAI/devin/1770222006-billing-tracking-guide
Add guide for tracking customer transcription usage (async + streaming)
2 parents b969185 + 917a1e0 commit 0549a1f

File tree

2 files changed

+352
-0
lines changed

2 files changed

+352
-0
lines changed

fern/docs.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,13 @@ navigation:
820820
- link: Speechmatics to AssemblyAI
821821
href: /docs/guides/speechmatics_to_aai_streaming
822822

823+
- section: Platform
824+
skip-slug: true
825+
contents:
826+
- page: Tracking customer transcription usage
827+
path: pages/05-guides/billing-tracking.mdx
828+
slug: tracking-your-customers-usage
829+
823830
# Legacy guides
824831
- page: Process Speaker Labels with LeMURs Custom Text Input Parameter
825832
path: pages/05-guides/cookbooks/lemur/input-text-speaker-labels.mdx
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
---
2+
title: "Tracking customer transcription usage"
3+
hide-nav-links: true
4+
description: "Learn how to track individual customer usage within your product using AssemblyAI's webhook system and metadata."
5+
---
6+
7+
This guide explains how to track individual customer usage within your product for billing purposes.
8+
9+
<Note>
10+
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.
11+
</Note>
12+
13+
There are two separate methods depending on which transcription approach you use:
14+
15+
1. **Async transcription**: Use webhooks with custom query parameters to associate transcriptions with customers, then retrieve the `audio_duration` from the transcript response.
16+
17+
2. **Streaming transcription**: Manage customer IDs in your application state and capture the `session_duration_seconds` from the WebSocket Termination event.
18+
19+
This guide covers both methods in detail.
20+
21+
## Async transcription usage tracking
22+
23+
By combining webhooks with custom metadata, you can track audio duration per customer and monitor their usage of your transcription service.
24+
25+
### Step 1: Set up webhooks with customer metadata
26+
27+
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.
28+
29+
<Tabs>
30+
<Tab language="python-sdk" title="Python SDK">
31+
32+
```python
33+
import assemblyai as aai
34+
35+
aai.settings.api_key = "<YOUR_API_KEY>"
36+
37+
# Add customer_id as a query parameter to your webhook URL
38+
webhook_url = "https://your-domain.com/webhook?customer_id=customer_123"
39+
40+
config = aai.TranscriptionConfig(
41+
speech_model=aai.SpeechModel.best
42+
).set_webhook(webhook_url)
43+
44+
# Submit without waiting for completion
45+
aai.Transcriber().submit("https://example.com/audio.mp3", config)
46+
```
47+
48+
</Tab>
49+
<Tab language="python" title="Python">
50+
51+
```python
52+
import requests
53+
54+
base_url = "https://api.assemblyai.com"
55+
headers = {"authorization": "<YOUR_API_KEY>"}
56+
57+
# Add customer_id as a query parameter to your webhook URL
58+
webhook_url = "https://your-domain.com/webhook?customer_id=customer_123"
59+
60+
data = {
61+
"audio_url": "https://example.com/audio.mp3",
62+
"webhook_url": webhook_url
63+
}
64+
65+
# Submit without waiting for completion
66+
response = requests.post(base_url + "/v2/transcript", headers=headers, json=data)
67+
transcript_id = response.json()["id"]
68+
```
69+
70+
</Tab>
71+
<Tab language="javascript-sdk" title="JavaScript SDK">
72+
73+
```javascript
74+
import { AssemblyAI } from "assemblyai";
75+
76+
const client = new AssemblyAI({
77+
apiKey: "<YOUR_API_KEY>",
78+
});
79+
80+
// Add customer_id as a query parameter to your webhook URL
81+
const webhookUrl = "https://your-domain.com/webhook?customer_id=customer_123";
82+
83+
const transcript = await client.transcripts.submit({
84+
audio: "https://example.com/audio.mp3",
85+
speech_model: "best",
86+
webhook_url: webhookUrl,
87+
});
88+
```
89+
90+
</Tab>
91+
<Tab language="javascript" title="JavaScript">
92+
93+
```javascript
94+
import axios from "axios";
95+
96+
const baseUrl = "https://api.assemblyai.com";
97+
const headers = { authorization: "<YOUR_API_KEY>" };
98+
99+
// Add customer_id as a query parameter to your webhook URL
100+
const webhookUrl = "https://your-domain.com/webhook?customer_id=customer_123";
101+
102+
const data = {
103+
audio_url: "https://example.com/audio.mp3",
104+
webhook_url: webhookUrl,
105+
};
106+
107+
// Submit without waiting for completion
108+
const response = await axios.post(`${baseUrl}/v2/transcript`, data, { headers });
109+
const transcriptId = response.data.id;
110+
```
111+
112+
</Tab>
113+
</Tabs>
114+
115+
You can add multiple query parameters to track additional information:
116+
117+
```
118+
https://your-domain.com/webhook?customer_id=123&project_id=456&order_id=789
119+
```
120+
121+
This allows you to track usage across multiple dimensions (customer, project, order, etc.).
122+
123+
### Step 2: Handle the webhook delivery
124+
125+
When the transcription completes, AssemblyAI sends a POST request to your webhook URL with the following payload:
126+
127+
```json
128+
{
129+
"transcript_id": "5552493-16d8-42d8-8feb-c2a16b56f6e8",
130+
"status": "completed"
131+
}
132+
```
133+
134+
Extract both the `transcript_id` from the payload and the `customer_id` from your URL query parameters.
135+
136+
### Step 3: Retrieve the transcript with audio duration
137+
138+
Use the transcript ID to fetch the complete transcript details, which includes the `audio_duration` field (in seconds).
139+
140+
<Tabs>
141+
<Tab language="python-sdk" title="Python SDK">
142+
143+
```python
144+
import assemblyai as aai
145+
146+
aai.settings.api_key = "<YOUR_API_KEY>"
147+
148+
# Get transcript using the ID from webhook
149+
transcript = aai.Transcript.get_by_id("<TRANSCRIPT_ID>")
150+
151+
if transcript.status == aai.TranscriptStatus.completed:
152+
audio_duration = transcript.audio_duration # Duration in seconds
153+
# Use audio_duration for billing/tracking
154+
```
155+
156+
</Tab>
157+
<Tab language="python" title="Python">
158+
159+
```python
160+
import requests
161+
162+
base_url = "https://api.assemblyai.com"
163+
headers = {"authorization": "<YOUR_API_KEY>"}
164+
165+
# Get transcript using the ID from webhook
166+
response = requests.get(base_url + "/v2/transcript/<TRANSCRIPT_ID>", headers=headers)
167+
transcript = response.json()
168+
169+
if transcript["status"] == "completed":
170+
audio_duration = transcript["audio_duration"] # Duration in seconds
171+
# Use audio_duration for billing/tracking
172+
```
173+
174+
</Tab>
175+
<Tab language="javascript-sdk" title="JavaScript SDK">
176+
177+
```javascript
178+
import { AssemblyAI } from "assemblyai";
179+
180+
const client = new AssemblyAI({
181+
apiKey: "<YOUR_API_KEY>",
182+
});
183+
184+
// Get transcript using the ID from webhook
185+
const transcript = await client.transcripts.get("<TRANSCRIPT_ID>");
186+
187+
if (transcript.status === "completed") {
188+
const audioDuration = transcript.audio_duration; // Duration in seconds
189+
// Use audioDuration for billing/tracking
190+
}
191+
```
192+
193+
</Tab>
194+
<Tab language="javascript" title="JavaScript">
195+
196+
```javascript
197+
import axios from "axios";
198+
199+
const baseUrl = "https://api.assemblyai.com";
200+
const headers = { authorization: "<YOUR_API_KEY>" };
201+
202+
// Get transcript using the ID from webhook
203+
const response = await axios.get(`${baseUrl}/v2/transcript/<TRANSCRIPT_ID>`, { headers });
204+
const transcript = response.data;
205+
206+
if (transcript.status === "completed") {
207+
const audioDuration = transcript.audio_duration; // Duration in seconds
208+
// Use audioDuration for billing/tracking
209+
}
210+
```
211+
212+
</Tab>
213+
</Tabs>
214+
215+
### Step 4: Track usage per customer
216+
217+
In your webhook handler, combine the customer ID from your webhook URL query parameters with the audio duration from the transcript to record usage:
218+
219+
1. Extract the `customer_id` from the webhook URL query parameters
220+
2. Extract the `transcript_id` from the webhook payload
221+
3. If the status is `completed`, fetch the transcript using the SDK to get the `audio_duration`
222+
4. Store the usage record in your database with the customer ID, transcript ID, audio duration, and timestamp
223+
224+
This allows you to aggregate usage per customer for billing purposes.
225+
226+
## Streaming transcription usage tracking
227+
228+
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.
229+
230+
### Step 1: Set up your WebSocket connection
231+
232+
Connect to AssemblyAI's streaming service. The customer ID is managed entirely in your application and is never sent to AssemblyAI.
233+
234+
```python
235+
import websocket
236+
import json
237+
from urllib.parse import urlencode
238+
from datetime import datetime
239+
240+
# Configuration
241+
YOUR_API_KEY = "<YOUR_API_KEY>"
242+
243+
CONNECTION_PARAMS = {
244+
"sample_rate": 16000,
245+
"format_turns": True,
246+
}
247+
248+
API_ENDPOINT = f"wss://streaming.assemblyai.com/v3/ws?{urlencode(CONNECTION_PARAMS)}"
249+
```
250+
251+
### Step 2: Capture audio duration from the Termination event
252+
253+
The key to tracking usage is capturing the `audio_duration_seconds` field from the Termination message. This is sent when the streaming session ends.
254+
255+
```python
256+
def on_message(ws, message):
257+
"""Handle WebSocket messages"""
258+
try:
259+
data = json.loads(message)
260+
msg_type = data.get("type")
261+
262+
if msg_type == "Begin":
263+
session_id = data.get("id")
264+
print(f"Session started: {session_id}")
265+
266+
elif msg_type == "Turn":
267+
transcript = data.get("transcript", "")
268+
if data.get("turn_is_formatted"):
269+
print(f"Transcript: {transcript}")
270+
271+
elif msg_type == "Termination":
272+
# Extract audio duration - this is what you need for billing
273+
audio_duration_seconds = data.get("audio_duration_seconds", 0)
274+
session_duration_seconds = data.get("session_duration_seconds", 0)
275+
276+
print(f"\nSession terminated:")
277+
print(f" Audio Duration: {audio_duration_seconds} seconds")
278+
print(f" Session Duration: {session_duration_seconds} seconds")
279+
280+
# Here you would associate audio_duration_seconds with your customer
281+
# using whatever session management system you have in place
282+
customer_id = get_customer_id_from_session() # Your implementation
283+
log_customer_usage(customer_id, session_duration_seconds)
284+
285+
except json.JSONDecodeError as e:
286+
print(f"Error decoding message: {e}")
287+
except Exception as e:
288+
print(f"Error handling message: {e}")
289+
```
290+
291+
### Step 3: Log customer usage
292+
293+
When you receive the Termination event, store the session duration for billing/tracking:
294+
295+
1. Retrieve the customer ID from your session management system (authentication tokens, session cookies, etc.)
296+
2. Extract the `session_duration_seconds` from the Termination event
297+
3. Store the usage record in your database with the customer ID, session duration, and timestamp
298+
299+
Since AssemblyAI bills streaming based on `session_duration_seconds`, this is the metric you should track for accurate billing.
300+
301+
### Session duration vs audio duration
302+
303+
From the Termination event, you receive two fields:
304+
305+
| Field | Description |
306+
|-------|-------------|
307+
| `session_duration_seconds` | Total time the session was open |
308+
| `audio_duration_seconds` | Total seconds of audio actually processed |
309+
310+
<Note>
311+
Streaming transcription is billed based on `session_duration_seconds`, not `audio_duration_seconds`. Make sure you track the correct metric for accurate billing.
312+
</Note>
313+
314+
### Session management
315+
316+
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.
317+
318+
### Proper session termination
319+
320+
Always close sessions properly to ensure you receive the Termination event and avoid unexpected costs:
321+
322+
```python
323+
# Send termination message when done
324+
terminate_message = {"type": "Terminate"}
325+
ws.send(json.dumps(terminate_message))
326+
```
327+
328+
## Best practices
329+
330+
When implementing billing tracking, consider the following best practices:
331+
332+
1. **Store the transcript/session ID**: Always store the identifier alongside usage records. This allows you to audit and verify billing data.
333+
334+
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.
335+
336+
3. **Secure your webhooks**: Use the `webhook_auth_header_name` and `webhook_auth_header_value` parameters to verify that webhook requests are from AssemblyAI.
337+
338+
4. **Consider time zones**: Store timestamps in UTC to avoid confusion when generating billing reports.
339+
340+
## Next steps
341+
342+
- Learn more about [webhooks](/docs/deployment/webhooks) and their configuration options
343+
- Explore the [Submit Transcript API](/docs/api-reference/transcripts/submit) for async transcription
344+
- Explore the [Get Transcript API](/docs/api-reference/transcripts/get) for retrieving transcript details
345+
- Review the [Streaming API](/docs/api-reference/streaming) for real-time transcription

0 commit comments

Comments
 (0)