BugTape docs
SaaS Integration Guide
Wire BugTape into a multi-tenant product.
How to integrate BugTape as a vendor into your B2B web application.
This guide is for teams building a SaaS product who want to use BugTape for bug reporting, either as the primary system or alongside an existing internal pipeline.
Architecture overview
┌─────────────────────────────────────────────────────┐
│ Your SaaS App (browser) │
│ │
│ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ BugTape SDK │──▶│ BugTape API (SaaS) │ │
│ │ (auto-capture│ │ POST /v1/ingest │ │
│ │ + widget) │ │ X-BugTape-Key: bt_live_xxx │ │
│ └──────────────┘ └────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ BugTape Console│ │
│ │ (bug inbox, │ │
│ │ AI analysis) │ │
│ └────────────────┘ │
│ │ │
│ Webhook ─┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Your Backend │ │
│ │ (sync-back) │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────┘
Integrating BugTape into a SaaS web app
1. Create a BugTape project per environment
Create separate projects for each of your environments:
| BugTape Project | API Key | Purpose |
|---|---|---|
myapp-production | bt_live_xxx | Production bug reports |
myapp-staging | bt_live_yyy | Staging/QA reports |
myapp-development | bt_test_zzz | Local dev testing |
2. Initialize once with the selected project key
Use the hosted module in a browser entry point after applying your app's consent policy. The public npm package is not currently published. Select the environment's capture key in your application configuration.
import { init } from 'https://app.bugtape.ai/bugtape.mjs';
const bt = init({
apiKey: 'YOUR_ENVIRONMENT_PROJECT_KEY',
release: 'YOUR_RELEASE_ID',
context: {
userId: currentUser.id,
environment: 'staging',
metadata: { tenantId: currentOrg.id },
},
});
// On the owning browser surface's teardown: bt.destroy();
For Next.js use the hosted module example. Keep initialization outside server execution and avoid initializing twice.
3. Keep tenant authorization on your server
Context fields label an occurrence; they are not an authorization boundary. A browser can change its submitted tenant ID. Use BugTape organization membership and project grants for access control. If your customers need separate console access, provision the appropriate organization/project boundary; projects alone do not isolate members of the same organization.
4. Set up webhooks for sync-back
Create a generic webhook integration in BugTape to receive notifications when bugs are filed:
POST /v1/integrations
{
"projectId": "your-project-uuid",
"type": "webhook",
"name": "Sync to internal system",
"config": {
"url": "https://your-app.com/api/webhooks/bugtape",
"secret": "your-hmac-secret"
},
"events": ["new_bug", "regression", "status_change"]
}
Your webhook handler receives:
{
"event": "new_bug",
"title": "Checkout fails on Safari",
"severity": "critical",
"url": "https://app.example.com/checkout",
"bugId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-02-28T12:00:00Z"
}
Verify the HMAC signature:
import crypto from 'crypto';
function verifyBugTapeSignature(body: string, signature: string, secret: string): boolean {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
if (typeof signature !== 'string') return false;
const received = Buffer.from(signature);
const wanted = Buffer.from(expected);
return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}
// In your webhook handler:
const sig = req.headers['x-bugtape-signature'] as string;
if (!verifyBugTapeSignature(rawBody, sig, process.env.BUGTAPE_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' });
}
Dual-write migration from internal bug reporting
If you have an existing internal bug reporting system and want to migrate to BugTape gradually:
Phase 1: Dual-write (both systems receive reports)
async function reportBug(report: BugReport) {
// Write to both systems in parallel
const [bugtapeResult, internalResult] = await Promise.allSettled([
submitToBugTape(report),
submitToInternalSystem(report),
]);
// Log any failures but don't block the user
if (bugtapeResult.status === 'rejected') {
console.warn('BugTape submission failed:', bugtapeResult.reason);
}
if (internalResult.status === 'rejected') {
console.warn('Internal submission failed:', internalResult.reason);
}
}
Phase 2: BugTape primary, internal fallback
async function reportBug(report: BugReport) {
try {
const result = await submitToBugTape(report);
// Optionally sync the BugTape ID to your internal system
await syncBugTapeId(result.id, report.internalRef);
return result;
} catch (error) {
// Fall back to internal system
console.warn('BugTape failed, falling back to internal:', error);
return submitToInternalSystem(report);
}
}
Phase 3: BugTape only
Remove the internal system. Use BugTape webhooks to sync relevant data back to your app.
How to avoid leaking secrets in frontend apps
What is browser-safe
| Item | Browser-safe? | Notes |
|---|---|---|
bt_live_* API key | Yes | Ingest-only, cannot read data |
bt_test_* API key | Yes | Same permissions as live |
| AI provider key | No | Keep on the server; omit openaiKey from browser configuration |
encryptionKey option | Unsupported | End-to-end report storage/read/decryption is unavailable |
| JWT tokens | No | Management API only, never expose to end users |
| Webhook secrets | No | Server-side only |
Recommendations
- Store API keys in environment variables, not in source code. Use
NEXT_PUBLIC_*prefix for browser-exposed values.
- Never expose JWT tokens to your end users. JWT tokens grant management access (read bugs, update status, manage team). They are for your team's console only.
- Keep AI provider keys server-side. Omit deprecated client-side provider configuration; set
summarize: falseto disable the summary request.
- Consider a server proxy if your security policy prohibits any API keys in client JavaScript. See integration and proxy guidance.
- Do not configure
encryptionKey. The SDK rejects this unsupported path; it does not provide end-to-end encrypted report storage.
How to tag tenants, projects, and customers safely
Approach A: Context events (recommended)
Push a context event into the buffer at initialization:
bt.buffer.push({
type: 'context',
timestamp: Date.now(),
data: {
tenantId: 'org_abc123',
tenantName: 'Acme Corp', // OK — this is your customer's name
userId: 'usr_xyz789',
userRole: 'admin',
release: 'v2.4.1',
environment: 'production',
},
});
This event is included while it remains in the bounded buffer. Use initialization context for fields that must accompany each occurrence.
Approach B: Reporter email
Include the user's email for attribution:
await fetch('https://app.bugtape.ai/v1/ingest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-BugTape-Key': 'bt_live_xxx',
},
body: JSON.stringify({
title: 'Report title',
reporterEmail: user.email,
events: bt.getEvents(),
}),
});
Approach C: Separate BugTape projects per customer
A project per customer separates capture keys and report grouping. It does not by itself isolate console access within an organization. Use organization membership and project grants to enforce the access boundary you need.
What NOT to include in events
- Passwords or credentials
- Full credit card numbers (PII scrubbing catches these, but don't rely on it)
- Internal service tokens
- Database connection strings
The SDK's PII scrubber catches common patterns (emails, credit cards, SSNs, JWTs, API keys), but it is a safety net, not a guarantee. Avoid putting secrets into console.log or network request bodies.
Deep links to BugTape
Report links
The BugTape console supports direct links to reports:
https://app.bugtape.ai/console/issues/<bug-id>
The bug-id is the UUID returned by the ingest API (response.id).
Building links from webhook data
When your webhook handler receives a bugId, construct a link:
function bugTapeUrl(bugId: string): string {
return `https://app.bugtape.ai/console/issues/${bugId}`;
}
Embedding links in your app
Show a "View in BugTape" link in your internal dashboard:
<a href="https://app.bugtape.ai/console/issues/a1b2c3d4" target="_blank">
View in BugTape
</a>
Search and filter conventions
The BugTape console supports filtering by:
| Filter | Description |
|---|---|
| Status | open, in_progress, resolved, closed |
| Severity | critical, high, medium, low |
| Project | Filter by project within org |
| Search | Full-text search in title and description |
| Sort | newest, oldest, severity, reports |
These same filters are available via the management API (GET /v1/bugs?status=open&severity=critical&search=checkout).
Polling for sync-back
If webhooks are not feasible, poll the BugTape API:
// Poll every 60 seconds for new bugs
async function pollBugTape(since: string) {
const response = await fetch(
`https://app.bugtape.ai/v1/bugs?sort=newest&limit=20`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-BugTape-Org': orgId,
},
}
);
const { bugs } = await response.json();
return bugs.filter((b: any) => b.created_at > since);
}
Recommendation: Prefer signed webhooks when your server can receive them. Delivery is asynchronous and subject to destination rules and retries; see agent delivery.