Instantly Security Basics
Overview
Secure your Instantly.ai integration with scoped API keys, least-privilege access, secret management, webhook validation, and audit logging. Instantly API v2 uses Bearer token auth with granular scope-based permissions.
Prerequisites
- Instantly account with API access
- Understanding of environment variable management
- Access to Instantly dashboard Settings > Integrations
Instructions
Step 1: Least-Privilege API Key Scopes
Create separate API keys for different use cases with minimal required scopes.
const ANALYTICS_KEY_SCOPES = ["campaigns:read", "accounts:read"];
const AUTOMATION_KEY_SCOPES = ["campaigns:all", "leads:all"];
const WEBHOOK_KEY_SCOPES = ["leads:read"];
| Use Case | Recommended Scopes | Risk Level |
|---|
| Analytics dashboard | campaigns:read, accounts:read | Low |
| Lead import tool | leads:update | Medium |
| Campaign launcher | campaigns:all, leads:all, accounts:read | High |
| Full automation | all:all | Critical — dev only |
| Webhook handler | leads:read | Low |
Step 2: Secret Management
const client = new InstantlyClient({ apiKey: "sk_live_abc123" });
const client = new InstantlyClient({
apiKey: process.env.INSTANTLY_API_KEY!,
});
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
async function getApiKey(): Promise<string> {
const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
name: "projects/my-project/secrets/instantly-api-key/versions/latest",
});
return version.payload?.data?.toString() || "";
}
.env
.env.*
*.key
Step 3: API Key Rotation
async function rotateApiKey() {
const oldKey = process.env.INSTANTLY_API_KEY;
const client = new InstantlyClient({ apiKey: process.env.INSTANTLY_API_KEY_NEW! });
await client.getCampaigns({ limit: 1 });
const keys = await client.request<Array<{ id: string; name: string }>>(
"/api-keys"
);
const oldKeyEntry = keys.find((k) => k.name === "old-key-name");
if (oldKeyEntry) {
await client.request(`/api-keys/${oldKeyEntry.id}`, { method: "DELETE" });
console.();
}
}
Step 4: Webhook Security
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/instantly", (req, res) => {
const expectedSecret = process.env.INSTANTLY_WEBHOOK_SECRET;
const receivedSecret = req.headers["x-webhook-secret"];
if (expectedSecret && receivedSecret !== expectedSecret) {
console.warn("Webhook auth failed: invalid secret");
return res.status(401).json({ error: "Unauthorized" });
}
const { event_type, data } = req.body;
if (!event_type || !data) {
return res.status(400).json({ error: "Invalid payload" });
}
res.status(200).json({ received: });
(event_type, data).(.);
});
() {
(, {
: ,
: .({
: ,
: targetUrl,
: ,
: {
: process..,
: ,
},
}),
});
}
Step 5: Audit Logging
async function checkAuditLogs() {
const logs = await instantly<Array<{
id: string;
action: string;
resource: string;
timestamp_created: string;
user: string;
}>>("/audit-logs?limit=50");
console.log("Recent Audit Events:");
for (const log of logs) {
console.log(` ${log.timestamp_created} | ${log.action} | ${log.resource} | ${log.user}`);
}
}
Step 6: Workspace Member Permissions
async function listWorkspaceMembers() {
const members = await instantly<Array<{
id: string;
email: string;
role: string;
}>>("/workspace-members");
for (const m of members) {
console.log(`${m.email}: ${m.role}`);
}
}
async function removeMember(memberId: string) {
await instantly(`/workspace-members/${memberId}`, { method: "DELETE" });
}
Security Checklist
Error Handling
| Error | Cause | Solution |
|---|
401 after rotation | Old key still in use | Verify deployment picked up new key |
403 on scope-limited key | Missing required scope | Create new key with correct scopes |
Webhook 401 | Secret mismatch | Check headers field in webhook config |
| Audit log empty | Plan doesn't include audit logs | Upgrade plan or check workspace settings |
Resources
Next Steps
For production readiness, see instantly-prod-checklist.