Skip to content

Conversation

@anikdhabal
Copy link
Contributor

@anikdhabal anikdhabal commented Jul 14, 2025

What does this PR do?

Removes the dependency on Revert.dev from the Pipedrive CRM integration and implements direct OAuth2 authentication with Pipedrive's native API. The changes follow the same patterns used by other CRM integrations in the codebase (particularly HubSpot and Zoom).

Summary

Key Changes:

  • OAuth Flow: Replaced Revert redirect with direct Pipedrive OAuth2 authorization
  • API Integration: Converted all API calls from Revert's unified format to Pipedrive's native REST API
  • Token Management: Implemented proper OAuth token refresh using Cal.com's OAuthManager pattern (same as Zoom, Google Calendar, etc.)
  • Environment Variables: Removed REVERT_* variables, now requires PIPEDRIVE_CLIENT_ID and PIPEDRIVE_CLIENT_SECRET
  • Data Mapping: Mapped contact and activity data between Revert's unified format and Pipedrive's native structure

Breaking Change: Existing Pipedrive integrations will need to be reconfigured with new OAuth credentials.

Updates since last revision

Refactored CrmService.ts to use Cal.com's OAuthManager for proper token management:

  • Added getPipedriveAppKeys helper function for client credentials extraction
  • Implemented requestPipedrive helper method that uses OAuthManager.request() for all API calls
  • Added proper token refresh callbacks (isTokenObjectUnusable, isAccessTokenUnusable) to detect Pipedrive error responses
  • Token refresh now works correctly using the same pattern as Zoom and other integrations

Diagram

%%{ init : { "theme" : "default" }}%%
graph TB
    subgraph "OAuth Flow"
        A["api/add.ts<br/>OAuth Authorization"]:::major-edit
        B["api/callback.ts<br/>Token Exchange"]:::major-edit
    end
    
    subgraph "CRM Integration"
        C["lib/CrmService.ts<br/>Pipedrive API Client"]:::major-edit
        D["lib/getPipedriveAppKeys.ts<br/>Credentials Helper"]:::major-edit
        E["config.json<br/>App Configuration"]:::minor-edit
    end
    
    subgraph "External APIs"
        F["Pipedrive OAuth API<br/>oauth.pipedrive.com"]:::context
        G["Pipedrive REST API<br/>api.pipedrive.com"]:::context
    end
    
    A --> B
    B --> C
    C --> G
    A --> F
    B --> F
    C --> D
    
    classDef major-edit fill:#90EE90
    classDef minor-edit fill:#ADD8E6
    classDef context fill:#FFFFFF
Loading

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - environment variable changes are documented in .env.appStore.example
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Environment Variables Required:

  • PIPEDRIVE_CLIENT_ID - OAuth client ID from Pipedrive developer portal
  • PIPEDRIVE_CLIENT_SECRET - OAuth client secret from Pipedrive developer portal

Test Plan:

  1. Set up a Pipedrive developer account and create an OAuth app
  2. Configure the environment variables above
  3. Test the integration setup flow in Cal.com (OAuth authorization)
  4. Create a test booking and verify it creates contacts and activities in Pipedrive
  5. Test updating and canceling bookings
  6. Important: Test token refresh by waiting for token expiry or manually expiring the token

Review Checklist for Human:

  • Verify OAuthManager integration correctly handles token refresh
  • Test isTokenObjectUnusable callback detects "invalid_grant" errors
  • Test isAccessTokenUnusable callback detects 401 responses
  • Verify api_domain is correctly preserved and updated in token object
  • Test contact creation and search with real Pipedrive data
  • Test activity/meeting CRUD operations

Notes

  • OAuth Scopes: Using comprehensive scopes: deals:read,deals:write,persons:read,persons:write,activities:read,activities:write
  • API Mapping: Activities are used for meetings/events instead of a separate events API, following Pipedrive's data model
  • Token Storage: Uses Cal.com's standard credential storage with expiry_date in milliseconds

Session Info:

- Replace Revert API calls with direct Pipedrive OAuth2 and REST API
- Update OAuth flow to use Pipedrive's authorization endpoint
- Implement token exchange in callback handler
- Map Revert's unified API format to Pipedrive's native API structure
- Remove REVERT_* environment variables from configuration
- Update app config to reflect direct Pipedrive integration
- Follow patterns from other CRM integrations like HubSpot

Breaking change: Requires PIPEDRIVE_CLIENT_ID and PIPEDRIVE_CLIENT_SECRET
environment variables instead of REVERT_* variables

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 14, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/remove-revert-pipedrive-1752520608

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the ❗️ .env changes contains changes to env variables label Jul 14, 2025
@vercel
Copy link

vercel bot commented Jul 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

4 Skipped Deployments
Project Deployment Review Updated (UTC)
api-v2 Ignored Ignored Preview Jan 7, 2026 5:41pm
cal Ignored Ignored Preview Jan 7, 2026 5:41pm
cal-companion Ignored Ignored Preview Jan 7, 2026 5:41pm
cal-eu Ignored Ignored Preview Jan 7, 2026 5:41pm

@keithwillcode keithwillcode added the core area: core, team members only label Jul 14, 2025
@delve-auditor
Copy link

delve-auditor bot commented Jul 14, 2025

No security or compliance issues detected. Reviewed everything up to 9b9d86b.

Security Overview
  • 🔎 Scanned files: 6 changed file(s)
Detected Code Changes
Change Type Relevant files
Configuration changes ► .env.appStore.example
    Remove REVERT-related environment variables
► turbo.json
    Remove REVERT environment variables
Enhancement ► add.ts
    Implement direct Pipedrive OAuth flow
► callback.ts
    Add Pipedrive token exchange handling
► config.json
    Update app configuration for direct Pipedrive integration
► CrmService.ts
    Replace Revert API with direct Pipedrive API implementation

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

- Add token refresh implementation following Pipedrive OAuth2 specification
- Use proper Basic Auth for token refresh requests
- Handle token refresh errors with appropriate logging
- Update access token and expiry date after successful refresh
- Add note about database credential update requirement

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@vercel vercel bot temporarily deployed to Preview – api July 14, 2025 19:50 Inactive
@vercel vercel bot temporarily deployed to Preview – cal July 14, 2025 19:50 Inactive
- Import updateTokenObjectInDb utility for proper credential management
- Store credentialId in constructor for database updates
- Update token refresh logic to persist refreshed tokens to database
- Follow Cal.com patterns for OAuth credential management
- Ensure tokens are properly updated after successful refresh

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
dakshgup added a commit to dakshgup/cal.com that referenced this pull request Jul 14, 2025
…drive integration

Originally by: anikdhabal

# Remove Revert.dev dependency from Pipedrive integration

## Summary

This PR removes the dependency on Revert.dev from the Pipedrive CRM integration and implements direct OAuth2 authentication with Pipedrive's native API. The changes follow the same patterns used by other CRM integrations in the codebase (particularly HubSpot).

**Key Changes:**
- **OAuth Flow**: Replaced Revert redirect with direct Pipedrive OAuth2 authorization
- **API Integration**: Converted all API calls from Revert's unified format to Pipedrive's native REST API
- **Token Management**: Implemented OAuth token storage and basic refresh logic
- **Environment Variables**: Removed `REVERT_*` variables, now requires `PIPEDRIVE_CLIENT_ID` and `PIPEDRIVE_CLIENT_SECRET`
- **Data Mapping**: Mapped contact and activity data between Revert's unified format and Pipedrive's native structure

**Breaking Change**: Existing Pipedrive integrations will need to be reconfigured with new OAuth credentials.

## Review & Testing Checklist for Human

- [ ] **Test OAuth flow end-to-end** - Create a Pipedrive OAuth app and verify the complete authorization flow works
- [ ] **Test contact creation and search** - Verify contacts can be created and searched properly with real data
- [ ] **Test activity/meeting management** - Verify activities can be created, updated, and deleted correctly
- [ ] **Verify token refresh logic** - The current implementation is incomplete and may need proper refresh token handling
- [ ] **Test error handling** - Verify proper error responses for failed API calls and invalid tokens

**Recommended Test Plan:**
1. Set up a Pipedrive developer account and create an OAuth app
2. Configure `PIPEDRIVE_CLIENT_ID` and `PIPEDRIVE_CLIENT_SECRET` in environment
3. Test the integration setup flow in Cal.com
4. Create a test booking and verify it creates contacts and activities in Pipedrive
5. Test updating and canceling bookings

---

### Diagram

```mermaid
%%{ init : { "theme" : "default" }}%%
graph TB
    subgraph "OAuth Flow"
        A["api/add.ts<br/>OAuth Authorization"]:::major-edit
        B["api/callback.ts<br/>Token Exchange"]:::major-edit
    end

    subgraph "CRM Integration"
        C["lib/CrmService.ts<br/>Pipedrive API Client"]:::major-edit
        D["config.json<br/>App Configuration"]:::minor-edit
    end

    subgraph "Configuration"
        E["turbo.json<br/>Environment Variables"]:::minor-edit
        F[".env.appStore.example<br/>Environment Template"]:::minor-edit
    end

    subgraph "External APIs"
        G["Pipedrive OAuth API<br/>oauth.pipedrive.com"]:::context
        H["Pipedrive REST API<br/>api.pipedrive.com"]:::context
    end

    A --> B
    B --> C
    C --> H
    A --> G
    B --> G
    C --> D

    subgraph Legend
        L1[Major Edit]:::major-edit
        L2[Minor Edit]:::minor-edit
        L3[Context/No Edit]:::context
    end

    classDef major-edit fill:#90EE90
    classDef minor-edit fill:#ADD8E6
    classDef context fill:#FFFFFF
```

### Notes

- **Token Refresh**: The current `getValidAccessToken()` implementation is incomplete - it doesn't actually refresh expired tokens, just returns the existing one. This should be addressed before production use.
- **OAuth Scopes**: Using comprehensive scopes: `deals:read,deals:write,persons:read,persons:write,activities:read,activities:write`
- **API Mapping**: Activities are used for meetings/events instead of a separate events API, following Pipedrive's data model
- **Error Handling**: Basic error handling is implemented but may need enhancement for production edge cases

**Session Info**:
- Link to Devin run: https://app.devin.ai/sessions/ca8c7c0a8b9b4b87ae92d5db2d23039a
- Requested by: @anikdhabal
@github-actions
Copy link
Contributor

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Jul 29, 2025
@github-actions github-actions bot added $50 community Created by Linear-GitHub Sync crm-apps area: crm apps, salesforce, hubspot, close.com, sendgrid Medium priority Created by Linear-GitHub Sync Stale ✨ feature New feature or request 💎 Bounty A bounty on Algora.io labels Aug 11, 2025
@github-actions github-actions bot added community Created by Linear-GitHub Sync Stale labels Sep 29, 2025
@anikdhabal anikdhabal removed Stale community Created by Linear-GitHub Sync labels Sep 29, 2025
@anikdhabal anikdhabal marked this pull request as ready for review October 22, 2025 14:55
@graphite-app graphite-app bot requested a review from a team October 22, 2025 14:55
@github-actions github-actions bot added community Created by Linear-GitHub Sync Stale labels Oct 22, 2025
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/app-store/pipedrive-crm/lib/CrmService.ts">

<violation number="1" location="packages/app-store/pipedrive-crm/lib/CrmService.ts:90">
After refreshing the token we still keep using the old refresh token in memory, so any rotated refresh_token will make the next refresh attempt fail. Please update the instance field when you persist the new token.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.


const refreshedToken = await tokenResponse.json();

this.accessToken = refreshedToken.access_token;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After refreshing the token we still keep using the old refresh token in memory, so any rotated refresh_token will make the next refresh attempt fail. Please update the instance field when you persist the new token.

Prompt for AI agents
Address the following comment on packages/app-store/pipedrive-crm/lib/CrmService.ts at line 90:

<comment>After refreshing the token we still keep using the old refresh token in memory, so any rotated refresh_token will make the next refresh attempt fail. Please update the instance field when you persist the new token.</comment>

<file context>
@@ -9,187 +9,287 @@ import type {
+
+      const refreshedToken = await tokenResponse.json();
+
+      this.accessToken = refreshedToken.access_token;
+      this.expiryDate = Math.round(Date.now() + refreshedToken.expires_in * 1000);
+
</file context>

✅ Addressed in c27c033

@keithwillcode keithwillcode requested review from a team and removed request for a team October 24, 2025 20:54
@hariombalhara
Copy link
Member

@anikdhabal Do we have a loom showing the e2e working after this change ?

Comment on lines 35 to 41
let clientId = "";
let clientSecret = "";
const appKeys = await getAppKeysFromSlug(appConfig.slug);
if (typeof appKeys.client_id === "string") clientId = appKeys.client_id;
if (typeof appKeys.client_secret === "string") clientSecret = appKeys.client_secret;
if (!clientId) return res.status(400).json({ message: "Pipedrive client id missing." });
if (!clientSecret) return res.status(400).json({ message: "Pipedrive client secret missing." });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to abstract it out in a separate fn

Comment on lines 60 to 68
const appKeys = await getAppKeysFromSlug(appConfig.slug);
let clientId = "";
let clientSecret = "";
if (typeof appKeys.client_id === "string") clientId = appKeys.client_id;
if (typeof appKeys.client_secret === "string") clientSecret = appKeys.client_secret;

if (!clientId || !clientSecret) {
throw new Error("Pipedrive client credentials missing for token refresh");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above abstracted fn could be reused here.

Copy link
Member

@hariombalhara hariombalhara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anikdhabal We have OAuthManager that we use for Google Calendar and Microsoft Office. I believe zoom too.
Can you check if we can use that here.

The benefit with OAuthManager approach is that it automatically handles invalidation of credential , plus supports credential sync feature out of the box.

@github-actions github-actions bot marked this pull request as draft October 28, 2025 12:57
@devin-ai-integration
Copy link
Contributor

❌ Cannot revive Devin session - the session is too old. Please start a new session instead.

- Refactor CrmService.ts to use OAuthManager instead of manual token refresh
- Create getPipedriveAppKeys helper function for client credentials
- Update callback.ts to use the new helper function
- Add requestPipedrive helper method for all API calls
- Implement proper token refresh callbacks (isTokenObjectUnusable, isAccessTokenUnusable)

This addresses the review comments from hariom and cubic on PR #22492.

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💎 Bounty A bounty on Algora.io community Created by Linear-GitHub Sync core area: core, team members only crm-apps area: crm apps, salesforce, hubspot, close.com, sendgrid ❗️ .env changes contains changes to env variables ✨ feature New feature or request Medium priority Created by Linear-GitHub Sync size/L Stale $50

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native Pipedrive integration

4 participants