Skip to main content

CallRail - Authentication

๐Ÿ“– Overviewโ€‹

CallRail authentication uses API keys generated from the CallRail dashboard. Keys are stored per DashClicks account in MongoDB and used for all API requests. The system supports soft deletion to preserve keys when campaigns are active, and includes automatic token invalidation detection.

Source Files:

  • Controller: external/Integrations/Callrail/Controllers/auth.js
  • Model: external/Integrations/Callrail/Models/keys.js
  • Routes: external/Integrations/Callrail/Routes/auth.js

External API: N/A (credential storage and management only)

๐Ÿ—„๏ธ Collections Usedโ€‹

integrations.callrail.keyโ€‹

  • Operations: Create, Read, Update, Delete
  • Model: shared/models/callrail-key.js
  • Usage Context: Store CallRail API keys with account association

Document Structure:

{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"account_id": ObjectId("507f1f77bcf86cd799439012"),
"api_key": "dfc7f89eaa8a9b4f0acbcd850a60a444",
"nickname": "Production CallRail",
"token_invalidated": false,
"deleted": ISODate("2023-10-01T12:00:00Z"), // Soft delete timestamp
"createdAt": ISODate("2023-09-15T10:00:00Z"),
"updatedAt": ISODate("2023-10-01T12:00:00Z")
}

analytics.callrail.userconfigโ€‹

  • Operations: Create, Read, Update, Delete
  • Model: shared/models/analytics-callrail-userconfig.js
  • Usage Context: Link CallRail account/company to DashClicks account for campaign tracking

Document Structure:

{
"_id": ObjectId,
"account_id": ObjectId,
"token": "507f1f77bcf86cd799439011", // Reference to callrail.key document ID
"callrail_acountid": "470306151",
"callrail_companyid": "com_abc123",
"createdAt": ISODate,
"updatedAt": ISODate
}

๐Ÿ”„ Data Flowโ€‹

API Key Registration Flowโ€‹

sequenceDiagram
participant Client as DashClicks Frontend
participant Controller as Auth Controller
participant KeysDB as MongoDB (callrail.key)

Client->>Controller: POST /v1/e/callrail/auth {api_key, nickname}
Controller->>KeysDB: Find existing key for account

alt Key exists and valid
KeysDB-->>Controller: Return existing key
Controller-->>Client: Return existing key document
else Key invalidated
KeysDB-->>Controller: Return invalidated key
Controller->>KeysDB: Delete invalidated key
Controller->>KeysDB: Save new key
KeysDB-->>Controller: New key document
Controller-->>Client: Return new key
else No key exists
KeysDB-->>Controller: No document found
Controller->>KeysDB: Save new key
KeysDB-->>Controller: Key document
Controller-->>Client: Return key document
end

User Analytics Config Flowโ€‹

sequenceDiagram
participant Client as DashClicks Frontend
participant Controller as Auth Controller
participant UserDB as MongoDB (User)
participant ConfigDB as MongoDB (analytics.callrail.userconfig)
participant FCM as FCM Service

Client->>Controller: POST /v1/e/callrail/auth/config
Controller->>UserDB: Find account owner
UserDB-->>Controller: Owner user document

Controller->>ConfigDB: Upsert config (account_id)
ConfigDB-->>Controller: Config saved

Controller->>FCM: Send notification (project added)
FCM-->>Controller: Notification sent

Controller-->>Client: {success: true, data: config}

๐Ÿ”ง Business Logic & Functionsโ€‹


Controller Functionsโ€‹

postApiKey(req, res, next)โ€‹

Purpose: Save or retrieve CallRail API key for account

Source: Controllers/auth.js

External API Endpoint: N/A (database operation only)

Parameters:

  • req.body.api_key (String) - CallRail API key
  • req.body.nickname (String) - Friendly name for the key
  • req.auth.account_id (ObjectId) - Account ID from JWT

Returns: JSON response with key document

{
"success": true,
"message": "SUCCESS",
"data": {
"id": "507f1f77bcf86cd799439011",
"account_id": "507f1f77bcf86cd799439012",
"api_key": "dfc7f89eaa8a9b4f0acbcd850a60a444",
"nickname": "Production CallRail"
}
}

Business Logic Flow:

  1. Validate Account Access

    • Call checkAccountAccess(req) to verify account permissions
    • Return error if invalid account ID
  2. Check Existing Key

    • Query database for existing key by account_id
    • Exclude soft-deleted keys (deleted: { $exists: false })
  3. Handle Existing Valid Key

    • If key exists and not invalidated, return existing key
    • Avoid creating duplicate keys
  4. Handle Invalidated Key

    • If key exists but token_invalidated: true
    • Delete all keys for account
    • Proceed to save new key
  5. Validate Required Fields

    • Check api_key and nickname provided
    • Return 400 error if missing
  6. Save New Key

    • Create document with api_key, account_id, nickname
    • Return saved document with generated ID

Request Example:

POST /v1/e/callrail/auth
Authorization: Bearer {jwt_token}
Content-Type: application/json

{
"api_key": "dfc7f89eaa8a9b4f0acbcd850a60a444",
"nickname": "Production CallRail"
}

Success Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"id": "507f1f77bcf86cd799439011",
"account_id": "507f1f77bcf86cd799439012",
"api_key": "dfc7f89eaa8a9b4f0acbcd850a60a444",
"nickname": "Production CallRail",
"createdAt": "2023-10-01T12:00:00.000Z",
"updatedAt": "2023-10-01T12:00:00.000Z"
}
}

Error Response (Missing Fields):

{
"success": false,
"errno": 400,
"message": "API Key and nickname are required.",
"code": "API_KEY_NICKNAME_REQUIRED"
}

Error Handling:

  • Invalid Account ID: Returns 400 with INVALID_ACCOUNT_ID
  • Missing api_key or nickname: Returns 400 with custom error
  • Database Errors: Propagated to error middleware

Example Usage:

// Save CallRail API key
await fetch('/v1/e/callrail/auth', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
api_key: 'dfc7f89eaa8a9b4f0acbcd850a60a444',
nickname: 'Production CallRail',
}),
});

Side Effects:

  • โš ๏ธ Database Write: Creates new API key document
  • โš ๏ธ Key Deletion: Deletes invalidated keys before creating new one
  • โ„น๏ธ Idempotent: Returns existing valid key without creating duplicate

saveUserAnalyticsConfig(req, res, next)โ€‹

Purpose: Save user-specific CallRail configuration for analytics integration

Source: Controllers/auth.js

External API Endpoint: N/A (database operation only)

Parameters:

  • req.body.token (String) - CallRail key document ID
  • req.body.account_id (String) - CallRail account ID
  • req.body.company_id (String) - CallRail company ID
  • req.auth.account_id (ObjectId) - DashClicks account ID

Returns: JSON response with config document

{
"success": true,
"message": "SUCCESS",
"data": {
"acknowledged": true,
"modifiedCount": 1,
"upsertedId": null,
"upsertedCount": 0,
"matchedCount": 1
}
}

Business Logic Flow:

  1. Validate Account Access

    • Verify account permissions
  2. Find Account Owner

    • Query User collection for account owner
    • Get owner's user_id for notifications
  3. Check Notification Preferences

    • Get user config for projects module
    • Check if project_added notifications enabled
  4. Validate Required Fields

    • Verify token, account_id, company_id provided
    • Return error if any missing
  5. Upsert Configuration

    • Update or insert config document
    • Link CallRail account/company to DashClicks account
  6. Send Notifications

    • If enabled, send FCM notification about integration added
    • Notification includes bell and browser types
  7. Return Success Response

Request Example:

POST /v1/e/callrail/auth/config
Authorization: Bearer {jwt_token}
Content-Type: application/json

{
"token": "507f1f77bcf86cd799439011",
"account_id": "470306151",
"company_id": "com_abc123"
}

Success Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"acknowledged": true,
"modifiedCount": 1
}
}

Error Response (Missing Fields):

{
"success": false,
"errno": 400,
"message": "account id, company_id and token are required."
}

Notification Sent:

{
"title": "Callrail integration added",
"body": "A new Callrail integration has been added.",
"data": { "subType": "bell" },
"module": "projects",
"type": "project_added"
}

Error Handling:

  • Invalid Account: Returns 400 with INVALID_ACCOUNT_ID
  • Missing Fields: Returns 400 with descriptive error
  • Notification Failure: Logged but doesn't fail request
  • Database Errors: Returns 400 status

Side Effects:

  • โš ๏ธ Database Upsert: Creates or updates config document
  • โš ๏ธ FCM Notification: Sends push notification to account owner
  • โ„น๏ธ Idempotent: Upsert ensures only one config per account

deleteApiKey(req, res, next)โ€‹

Purpose: Delete CallRail API key with campaign preservation logic

Source: Controllers/auth.js

External API Endpoint: N/A (database operations only)

Parameters:

  • req.query.global (String, optional) - "true" for complete removal
  • req.auth.account_id (ObjectId) - Account ID
  • req.auth.uid (ObjectId) - User ID
  • req.auth.parent_account (ObjectId, optional) - Parent account for sub-accounts

Returns: JSON response

{
"success": true,
"message": "SUCCESS"
}

Business Logic Flow:

  1. Validate Account Access

    • Verify account permissions
  2. Find Account Owner and Preferences

    • Get account owner user document
    • Get user notification preferences for analytics
  3. Check Global Delete Flag

    If global=true (Complete Removal):

    a. Disable Active Campaigns

    • Find all active CallRail campaigns
    • Mark campaigns as deleted (is_deleted: true)

    b. Delete User Config

    • Remove analytics.callrail.userconfig document

    c. Soft Delete API Key

    • Update key with deleted: new Date()
    • Key remains in database but marked deleted

    d. Send Notifications

    • FCM notification (browser/bell if enabled)
    • Email notification (if enabled)
    • Send to parent account if sub-account

    If global=false or not provided (Conditional Delete):

    a. Check Existing Campaigns

    • Query for active CallRail campaigns

    b. If Campaigns Exist

    • Only delete user config
    • Keep API key intact for existing campaigns
    • Send notifications

    c. If No Campaigns

    • Delete user config
    • Soft delete API key
    • Send notifications
  4. Return Success Response

Request Examples:

# Complete removal (delete everything)
DELETE /v1/e/callrail/auth?global=true
Authorization: Bearer {jwt_token}

# Conditional removal (preserve if campaigns exist)
DELETE /v1/e/callrail/auth
Authorization: Bearer {jwt_token}

Success Response:

{
"success": true,
"message": "SUCCESS"
}

Notification Examples:

FCM Notification:

{
"title": "CallRail integration is Disconnected",
"body": "CallRail integration has been disconnected.",
"data": { "subType": "bell" },
"module": "analytics",
"type": "integration_disconnected"
}

Email Notification:

{
"subject": "CallRail integration disconnected",
"content": "CallRail integration has been disconnected.",
"origin": "analytics",
"recipients": [{
"name": "John Doe",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe"
}]
}

Error Handling:

  • Invalid Account: Returns 400 with INVALID_ACCOUNT_ID
  • Database Errors: Passed to error middleware
  • Notification Failures: Logged but don't fail request

Side Effects:

  • โš ๏ธ Campaign Updates: May mark campaigns as deleted
  • โš ๏ธ Config Deletion: Removes user config document
  • โš ๏ธ Soft Delete: Marks API key as deleted (not removed)
  • โš ๏ธ Notifications: Sends email and FCM notifications
  • โš ๏ธ Parent Account: May send notifications to parent account

Model Functionsโ€‹

save(authData)โ€‹

Purpose: Save new API key document

Source: Models/keys.js

Parameters:

  • authData (Object) - Key data to save
    • api_key (String) - CallRail API key
    • account_id (ObjectId) - Account ID
    • nickname (String) - Key nickname

Returns: Promise<Object> - Saved document with ID

{
"id": "507f1f77bcf86cd799439011",
"account_id": "507f1f77bcf86cd799439012",
"api_key": "dfc7f89eaa8a9b4f0acbcd850a60a444",
"nickname": "Production CallRail"
}

Example Usage:

const authData = {
api_key: 'dfc7f89eaa8a9b4f0acbcd850a60a444',
account_id: new mongoose.Types.ObjectId(accountId),
nickname: 'Production CallRail',
};

const result = await keysModel.save(authData);
// Returns: { id: '...', api_key: '...', ... }

Side Effects:

  • โš ๏ธ Database Write: Creates new document in callrail.key collection

findApiKey(condition)โ€‹

Purpose: Find API key document by conditions

Source: Models/keys.js

Parameters:

  • condition (Object) - Query conditions
    • account_id (String|ObjectId) - Account ID
    • _id (String|ObjectId, optional) - Document ID

Returns: Promise<Object|false> - Key document or false if not found

Example Usage:

const key = await keysModel.findApiKey({
account_id: accountId,
_id: tokenId,
});

if (key) {
// Use key.api_key for API calls
}

Side Effects:

  • โ„น๏ธ Database Read: Queries callrail.key collection

deleteAll(accountId)โ€‹

Purpose: Delete all API keys for account (hard delete)

Source: Models/keys.js

Parameters:

  • accountId (ObjectId) - Account ID

Returns: Promise<Object> - Delete result

Example Usage:

await keysModel.deleteAll(new mongoose.Types.ObjectId(accountId));
// All keys for account deleted

Side Effects:

  • โš ๏ธ Database Delete: Removes documents from database

update(condition, updateData)โ€‹

Purpose: Update multiple key documents (used for soft delete)

Source: Models/keys.js

Parameters:

  • condition (Object) - Query conditions
  • updateData (Object) - Update operations

Returns: Promise<Object> - Update result

Example Usage:

// Soft delete
await keysModel.update(
{ account_id: new mongoose.Types.ObjectId(accountId) },
{ deleted: new Date() },
);

// Mark as invalidated
await keysModel.update(
{ account_id: new mongoose.Types.ObjectId(accountId) },
{ $set: { token_invalidated: true } },
);

Side Effects:

  • โš ๏ธ Database Update: Modifies multiple documents

๐Ÿ”€ Integration Pointsโ€‹

Campaign Integrationโ€‹

Key Preservation Logic:

  • API keys preserved if active campaigns exist
  • Prevents breaking campaign tracking mid-flight
  • Only user config deleted when campaigns active

Campaign Check:

const existingCampaigns = await campaignModel.findOne({
account_id: accountId,
is_deleted: false,
integration: 'callrail',
});

if (existingCampaigns) {
// Only delete user config, keep API key
} else {
// Safe to delete both config and key
}

Notification Systemโ€‹

Integration Points:

  • FCM notifications for integration connect/disconnect
  • Email notifications based on user preferences
  • Parent account notifications for sub-accounts

Notification Types:

  • project_added - When integration added
  • integration_disconnected - When integration removed

๐Ÿงช Edge Cases & Special Handlingโ€‹

Invalidated Keysโ€‹

Issue: CallRail API returns 401 when key is invalid

Handling:

  • Main error handler in index.js catches 401 errors
  • Sets token_invalidated: true on all account keys
  • User must re-authenticate with new key

Soft Delete vs Hard Deleteโ€‹

Issue: Need to preserve keys for active campaigns

Handling:

  • Soft Delete: Set deleted timestamp, key remains in database
  • Hard Delete: deleteAll() removes documents completely
  • Soft delete used by default to preserve historical data

Multiple Keys Per Accountโ€‹

Issue: System designed for one key per account

Handling:

  • findApiKey uses findOne() - returns first match
  • deleteAll removes all keys when invalidated
  • UI should prevent creating multiple valid keys

Sub-Account Notificationsโ€‹

Issue: Sub-accounts may need to notify parent account

Handling:

  • Check if parent_account exists and differs from account_id
  • Send duplicate notifications to parent account
  • Notifications indicate "for projects" context

โš ๏ธ Important Notesโ€‹

  • ๐Ÿ” API Key Format: 32-character hexadecimal string from CallRail dashboard
  • ๐Ÿ—‘๏ธ Soft Delete: Keys marked as deleted, not removed from database
  • ๐Ÿ“Š Campaign Preservation: Keys kept if active campaigns exist
  • ๐Ÿ”„ Token Invalidation: Automatic detection on 401 errors
  • ๐Ÿ”” Notifications: Email and FCM notifications on connect/disconnect
  • ๐Ÿ‘ฅ Parent Account: Sub-accounts send notifications to parent
  • ๐Ÿ“ User Config: Separate document links CallRail to DashClicks campaigns
  • โš ๏ธ Global Delete: Use global=true query param for complete removal

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM