Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Vertriebsautomatisierung und CRM-Integration für Schließen",
"Your Close CRM API key for authentication.": "Ihr Schließen CRM API Schlüssel für die Authentifizierung.",
"Create Lead": "Lead erstellen",
"Create Contact": "Kontakt erstellen",
"Find Lead": "Lead finden",
"Create Opportunity": "Verkaufschance erstellen",
"Find Contact": "Kontakt finden",
"Custom API Call": "Eigener API-Aufruf",
"Creates a new lead.": "Erstellt einen neuen Vorsprung.",
"Creates a new contact.": "Erstellt einen neuen Kontakt.",
"Search for leads with advanced filtering options": "Suche nach Leads mit erweiterten Filteroptionen",
"Create a new opportunity.": "Erstelle eine neue Chance.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Suche nach Kontakten nach Namen, E-Mail oder anderen Kriterien mit erweiterter Filterung",
"Make a custom API call to a specific endpoint": "Einen benutzerdefinierten API-Aufruf an einen bestimmten Endpunkt machen",
"Lead Name": "Leadname",
"URL": "URL",
"Description": "Beschreibung",
"Contacts": "Kontakte",
"Status": "Status",
"Custom Fields": "Eigene Felder",
"Lead": "Lead",
"Name": "Name",
"Title": "Titel",
"Office Phone": "Bürotelefon",
"Mobile Phone": "Handy",
"Home Phone": "Telefon privat",
"Direct Phone": "Direktes Telefon",
"Fax Phone": "Faxtelefon",
"Other Phone": "Anderes Telefon",
"Office Email": "Büro-E-Mail",
"Home Email": "Home-E-Mail",
"Direct Email": "Direkte E-Mail",
"Other Email": "Andere E-Mail",
"Search Type": "Suchtyp",
"Search Query": "Suchanfrage",
"Match Type": "Match-Typ",
"Opportunity Name": "Opportunity Name",
"Notes": "Notizen",
"Confidence %": "Vertrauen %",
"Value": "Wert",
"Value Period": "Wertezeitraum",
"Contact ID": "Kontakt-ID",
"User": "Benutzer",
"Method": "Methode",
"Headers": "Kopfzeilen",
"Query Parameters": "Abfrageparameter",
"Body": "Körper",
"Response is Binary ?": "Antwort ist binär?",
"No Error on Failure": "Kein Fehler bei Fehler",
"Timeout (in seconds)": "Timeout (in Sekunden)",
"The name of the lead/company.": "Der Name der Blei/Firma.",
"Array of contact details for this lead": "Anordnung der Kontaktdaten für diesen Lead",
"A descriptive name for the opportunity.": "Ein beschreibender Name für die Gelegenheit.",
"Additional details about the opportunity.": "Weitere Details zur Chance.",
"The probability of winning this opportunity (0-100).": "Die Wahrscheinlichkeit, diese Chance zu gewinnen (0-100).",
"The period for the opportunity value.": "Der Zeitraum für den Opportunitätswert.",
"The ID of the contact associated with this opportunity.": "Die ID des Kontakts, der mit dieser Möglichkeit verbunden ist.",
"Authorization headers are injected automatically from your connection.": "Autorisierungs-Header werden automatisch von Ihrer Verbindung injiziert.",
"Enable for files like PDFs, images, etc..": "Aktivieren für Dateien wie PDFs, Bilder, etc..",
"By Name": "Nach Name",
"By Contact Email": "Per Kontakt E-Mail",
"By Status": "Nach Status",
"Contains": "Enthält",
"Exact Match": "Genaues Match",
"Starts With": "Beginnt mit",
"Ends With": "Endet mit",
"One-Time": "Einmal",
"Monthly": "Monatlich",
"Annual": "Jährlich",
"By Email": "Per E-Mail",
"By Phone": "Per Telefon",
"By Lead ID": "Nach Lead-ID",
"GET": "ERHALTEN",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "LÖSCHEN",
"HEAD": "HEAD",
"New Lead Created": "Neuer Lead erstellt",
"New Contact Added": "Neuer Kontakt hinzugefügt",
"New Opportunity Added": "Neue Chance hinzugefügt",
"Triggers when a new lead is created.": "Wird ausgelöst, wenn ein neuer Lead erstellt wird.",
"Triggers when a new contact is created.": "Wird ausgelöst, wenn ein neuer Kontakt erstellt wird.",
"Triggers when a new opportunity is created.": "Wird ausgelöst, wenn eine neue Chance geschaffen wird."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Automatización de ventas e integración CRM para cerrar",
"Your Close CRM API key for authentication.": "Su clave API de cierre para autenticación.",
"Create Lead": "Crear plomo",
"Create Contact": "Crear contacto",
"Find Lead": "Buscar plomo",
"Create Opportunity": "Crear Oportunidad",
"Find Contact": "Encontrar contacto",
"Custom API Call": "Llamada API personalizada",
"Creates a new lead.": "Crea un nuevo cliente potencial.",
"Creates a new contact.": "Crea un nuevo contacto.",
"Search for leads with advanced filtering options": "Buscar clientes potenciales con opciones avanzadas de filtrado",
"Create a new opportunity.": "Crear una nueva oportunidad.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Buscar contactos por nombre, correo electrónico u otros criterios con filtrado avanzado",
"Make a custom API call to a specific endpoint": "Hacer una llamada API personalizada a un extremo específico",
"Lead Name": "Nombre del jefe",
"URL": "URL",
"Description": "Descripción",
"Contacts": "Contactos",
"Status": "Estado",
"Custom Fields": "Campos personalizados",
"Lead": "Plomo",
"Name": "Nombre",
"Title": "Título",
"Office Phone": "Teléfono de oficina",
"Mobile Phone": "Teléfono móvil",
"Home Phone": "Teléfono",
"Direct Phone": "Teléfono directo",
"Fax Phone": "Teléfono fax",
"Other Phone": "Otro teléfono",
"Office Email": "Email de oficina",
"Home Email": "Email de inicio",
"Direct Email": "Email directo",
"Other Email": "Otro Email",
"Search Type": "Tipo de búsqueda",
"Search Query": "Buscar consulta",
"Match Type": "Tipo de partida",
"Opportunity Name": "Nombre de la oportunidad",
"Notes": "Notas",
"Confidence %": "% de confianza",
"Value": "Valor",
"Value Period": "Valor del período",
"Contact ID": "ID de contacto",
"User": "Usuario",
"Method": "Método",
"Headers": "Encabezados",
"Query Parameters": "Parámetros de consulta",
"Body": "Cuerpo",
"Response is Binary ?": "¿Respuesta es binaria?",
"No Error on Failure": "No hay ningún error en fallo",
"Timeout (in seconds)": "Tiempo de espera (en segundos)",
"The name of the lead/company.": "El nombre de la lead/empresa.",
"Array of contact details for this lead": "Arreglo de datos de contacto para este cliente",
"A descriptive name for the opportunity.": "Un nombre descriptivo para la oportunidad.",
"Additional details about the opportunity.": "Más detalles sobre la oportunidad.",
"The probability of winning this opportunity (0-100).": "La probabilidad de ganar esta oportunidad (0-100).",
"The period for the opportunity value.": "El período para el valor de la oportunidad.",
"The ID of the contact associated with this opportunity.": "El ID del contacto asociado con esta oportunidad.",
"Authorization headers are injected automatically from your connection.": "Las cabeceras de autorización se inyectan automáticamente desde tu conexión.",
"Enable for files like PDFs, images, etc..": "Activar para archivos como PDFs, imágenes, etc.",
"By Name": "Por nombre",
"By Contact Email": "Por Email de Contacto",
"By Status": "Por Estado",
"Contains": "Contiene",
"Exact Match": "Partida exacta",
"Starts With": "Comienza por",
"Ends With": "Termina con",
"One-Time": "Una vez",
"Monthly": "Mensual",
"Annual": "Anual",
"By Email": "Por Email",
"By Phone": "Por teléfono",
"By Lead ID": "Por ID de plomo",
"GET": "RECOGER",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "BORRAR",
"HEAD": "LIMPIO",
"New Lead Created": "Nuevo plomo creado",
"New Contact Added": "Nuevo contacto añadido",
"New Opportunity Added": "Nueva oportunidad añadida",
"Triggers when a new lead is created.": "Dispara cuando se crea un nuevo plomo.",
"Triggers when a new contact is created.": "Dispara cuando se crea un nuevo contacto.",
"Triggers when a new opportunity is created.": "Dispara cuando se crea una nueva oportunidad."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Automatisation des ventes et intégration CRM pour la fermeture",
"Your Close CRM API key for authentication.": "Fermer la clé d'API CRM pour l'authentification.",
"Create Lead": "Créer Prospect",
"Create Contact": "Créer un contact",
"Find Lead": "Trouver un prospect",
"Create Opportunity": "Créer une Affaire",
"Find Contact": "Trouver un contact",
"Custom API Call": "Appel API personnalisé",
"Creates a new lead.": "Crée un nouveau prospect.",
"Creates a new contact.": "Crée un nouveau contact.",
"Search for leads with advanced filtering options": "Rechercher des prospects avec des options de filtrage avancées",
"Create a new opportunity.": "Créer une nouvelle opportunité.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Rechercher des contacts par nom, e-mail ou autres critères de filtrage avancé",
"Make a custom API call to a specific endpoint": "Passez un appel API personnalisé à un point de terminaison spécifique",
"Lead Name": "Nom du prospect",
"URL": "URL",
"Description": "Libellé",
"Contacts": "Contacts",
"Status": "Statut",
"Custom Fields": "Champs personnalisés",
"Lead": "Prospect",
"Name": "Nom",
"Title": "Titre de la page",
"Office Phone": "Téléphone de bureau",
"Mobile Phone": "Téléphone mobile",
"Home Phone": "Téléphone de la maison",
"Direct Phone": "Téléphone direct",
"Fax Phone": "Téléphone Fax",
"Other Phone": "Autre téléphone",
"Office Email": "E-mail du bureau",
"Home Email": "E-mail de la maison",
"Direct Email": "E-mail direct",
"Other Email": "Autre Email",
"Search Type": "Type de recherche",
"Search Query": "Requête de recherche",
"Match Type": "Type de correspondance",
"Opportunity Name": "Nom de l'opportunité",
"Notes": "Notes",
"Confidence %": "% de confiance",
"Value": "Valeur",
"Value Period": "Période de valeur",
"Contact ID": "ID du contact",
"User": "Utilisateur",
"Method": "Méthode",
"Headers": "En-têtes",
"Query Parameters": "Paramètres de requête",
"Body": "Corps",
"Response is Binary ?": "La réponse est Binaire ?",
"No Error on Failure": "Aucune erreur en cas d'échec",
"Timeout (in seconds)": "Délai d'attente (en secondes)",
"The name of the lead/company.": "Le nom du plomb/de la société.",
"Array of contact details for this lead": "Tableau des coordonnées pour ce prospect",
"A descriptive name for the opportunity.": "Un nom descriptif pour l'opportunité.",
"Additional details about the opportunity.": "Plus de détails sur l'opportunité.",
"The probability of winning this opportunity (0-100).": "La probabilité de gagner cette opportunité (0-100).",
"The period for the opportunity value.": "La période pour la valeur de l'opportunité.",
"The ID of the contact associated with this opportunity.": "L'ID du contact associé à cette opportunité.",
"Authorization headers are injected automatically from your connection.": "Les en-têtes d'autorisation sont injectés automatiquement à partir de votre connexion.",
"Enable for files like PDFs, images, etc..": "Activer pour les fichiers comme les PDFs, les images, etc.",
"By Name": "Par nom",
"By Contact Email": "Par e-mail de contact",
"By Status": "Par statut",
"Contains": "Contient",
"Exact Match": "Correspondance exacte",
"Starts With": "Commence avec",
"Ends With": "Se termine par",
"One-Time": "Une fois",
"Monthly": "Mensuel",
"Annual": "Annuel",
"By Email": "Par e-mail",
"By Phone": "Par téléphone",
"By Lead ID": "Par ID de prospect",
"GET": "OBTENIR",
"POST": "POSTER",
"PATCH": "PATCH",
"PUT": "EFFACER",
"DELETE": "SUPPRIMER",
"HEAD": "TÊTE",
"New Lead Created": "Nouveau prospect créé",
"New Contact Added": "Nouveau contact ajouté",
"New Opportunity Added": "Nouvelle opportunité ajoutée",
"Triggers when a new lead is created.": "Déclenche lorsqu'un nouveau prospect est créé.",
"Triggers when a new contact is created.": "Déclenche lorsqu'un nouveau contact est créé.",
"Triggers when a new opportunity is created.": "Déclenche quand une nouvelle opportunité est créée."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "閉じるためのセールスオートメーションとCRM統合",
"Your Close CRM API key for authentication.": "認証のためにCRMを閉じるAPIキー",
"Create Lead": "リードを作成",
"Create Contact": "連絡先を作成",
"Find Lead": "リードを見つける",
"Create Opportunity": "案件を作成",
"Find Contact": "連絡先を探す",
"Custom API Call": "カスタムAPI通話",
"Creates a new lead.": "新しいリードを作成します。",
"Creates a new contact.": "新しい連絡先を作成します。",
"Search for leads with advanced filtering options": "高度なフィルタリングオプションでリードを検索",
"Create a new opportunity.": "新しい機会を作りましょう。",
"Search for contacts by name, email, or other criteria with advanced filtering": "詳細なフィルタリングで名前、電子メール、またはその他の条件で連絡先を検索します",
"Make a custom API call to a specific endpoint": "特定のエンドポイントへのカスタム API コールを実行します。",
"Lead Name": "リード名",
"URL": "URL",
"Description": "説明",
"Contacts": "連絡先",
"Status": "ステータス",
"Custom Fields": "カスタムフィールド",
"Lead": "リード",
"Name": "名前",
"Title": "タイトル",
"Office Phone": "勤務先電話番号",
"Mobile Phone": "携帯電話",
"Home Phone": "自宅電話",
"Direct Phone": "直接電話",
"Fax Phone": "FAX電話",
"Other Phone": "その他の電話",
"Office Email": "オフィスのメールアドレス",
"Home Email": "ホームメール",
"Direct Email": "ダイレクトメール",
"Other Email": "その他のメール",
"Search Type": "検索タイプ",
"Search Query": "検索クエリ",
"Match Type": "一致するタイプ",
"Opportunity Name": "商談名",
"Notes": "メモ",
"Confidence %": "自信度%",
"Value": "値",
"Value Period": "値の期間",
"Contact ID": "連絡先ID",
"User": "ユーザー",
"Method": "方法",
"Headers": "ヘッダー",
"Query Parameters": "クエリパラメータ",
"Body": "本文",
"Response is Binary ?": "応答はバイナリですか?",
"No Error on Failure": "失敗時にエラーはありません",
"Timeout (in seconds)": "タイムアウト(秒)",
"The name of the lead/company.": "リード/会社の名前。",
"Array of contact details for this lead": "このリードの連絡先詳細の配列",
"A descriptive name for the opportunity.": "機会の説明的な名前。",
"Additional details about the opportunity.": "機会に関する追加の詳細。",
"The probability of winning this opportunity (0-100).": "この機会に勝つ確率(0-100)。",
"The period for the opportunity value.": "商談値の期間。",
"The ID of the contact associated with this opportunity.": "この商談に関連付けられている連絡先のID。",
"Authorization headers are injected automatically from your connection.": "認証ヘッダは接続から自動的に注入されます。",
"Enable for files like PDFs, images, etc..": "PDF、画像などのファイルを有効にします。",
"By Name": "名前順",
"By Contact Email": "連絡先のメールアドレスによって",
"By Status": "ステータス順",
"Contains": "以下を含む",
"Exact Match": "完全一致",
"Starts With": "で始まる",
"Ends With": "で終了",
"One-Time": "ワンタイム",
"Monthly": "月ごと",
"Annual": "年間",
"By Email": "メール順",
"By Phone": "電話",
"By Lead ID": "見込み客ID",
"GET": "取得",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "削除",
"HEAD": "頭",
"New Lead Created": "新しいリードが作成されました",
"New Contact Added": "新しい連絡先が追加されました",
"New Opportunity Added": "新しい商談が追加されました",
"Triggers when a new lead is created.": "新しいリードが作成されたときにトリガーします。",
"Triggers when a new contact is created.": "新しい連絡先が作成されたときにトリガーします.",
"Triggers when a new opportunity is created.": "新しい商談が作成されたときにトリガーします。"
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Verkoopautomatisering en CRM-integratie voor sluiten",
"Your Close CRM API key for authentication.": "Uw SluitCRM API-sleutel voor authenticatie.",
"Create Lead": "Maak Lead",
"Create Contact": "Contactpersoon aanmaken",
"Find Lead": "Zoek Lead",
"Create Opportunity": "Verkoopkans creëren",
"Find Contact": "Contactpersoon zoeken",
"Custom API Call": "Custom API Call",
"Creates a new lead.": "Maakt een nieuwe lood.",
"Creates a new contact.": "Maakt een nieuw contact aan.",
"Search for leads with advanced filtering options": "Zoeken naar leads met geavanceerde filteropties",
"Create a new opportunity.": "Maak een nieuwe kans.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Zoek naar contactpersonen op naam, e-mail of andere criteria met geavanceerde filtering",
"Make a custom API call to a specific endpoint": "Maak een aangepaste API call naar een specifiek eindpunt",
"Lead Name": "Lead naam",
"URL": "URL",
"Description": "Beschrijving",
"Contacts": "Contactpersonen",
"Status": "status",
"Custom Fields": "Aangepaste velden",
"Lead": "Lood",
"Name": "Naam",
"Title": "Aanspreektitel",
"Office Phone": "Telefoon Kantoor",
"Mobile Phone": "Telefoon (mobiel)",
"Home Phone": "Telefoon (thuis)",
"Direct Phone": "Directe telefoon",
"Fax Phone": "Fax Telefoon",
"Other Phone": "Telefoon (over)",
"Office Email": "Kantoor E-mail",
"Home Email": "Thuis e-mail",
"Direct Email": "Directe e-mail",
"Other Email": "Andere E-mail",
"Search Type": "Type zoeken",
"Search Query": "Zoek query",
"Match Type": "Wedstrijd Type",
"Opportunity Name": "Verkoopkans naam",
"Notes": "Opmerkingen",
"Confidence %": "Vertrouwen %",
"Value": "Waarde",
"Value Period": "Waarde periode",
"Contact ID": "Contact ID",
"User": "Gebruiker",
"Method": "Methode",
"Headers": "Kopteksten",
"Query Parameters": "Query parameters",
"Body": "Lichaam",
"Response is Binary ?": "Antwoord is binair?",
"No Error on Failure": "Geen fout bij fout",
"Timeout (in seconds)": "Time-out (in seconden)",
"The name of the lead/company.": "De naam van het lead/bedrijf.",
"Array of contact details for this lead": "Reeks van contactgegevens voor deze lead",
"A descriptive name for the opportunity.": "Een beschrijvende naam voor de kans.",
"Additional details about the opportunity.": "Aanvullende details over de mogelijkheid.",
"The probability of winning this opportunity (0-100).": "De kans dat deze kans wordt gewonnen (0-100).",
"The period for the opportunity value.": "De periode voor de opportuniteitswaarde.",
"The ID of the contact associated with this opportunity.": "Het ID van het contact dat is gekoppeld aan deze mogelijkheid.",
"Authorization headers are injected automatically from your connection.": "Autorisatie headers worden automatisch geïnjecteerd vanuit uw verbinding.",
"Enable for files like PDFs, images, etc..": "Inschakelen voor bestanden zoals PDF's, afbeeldingen etc..",
"By Name": "Op naam",
"By Contact Email": "Per contact e-mail",
"By Status": "door Status",
"Contains": "Bevat",
"Exact Match": "Exacte overeenkomst",
"Starts With": "Begint met",
"Ends With": "Eindigt met",
"One-Time": "Eenmalig",
"Monthly": "maandelijks",
"Annual": "Jaarlijks",
"By Email": "Per e-mail",
"By Phone": "Per telefoon",
"By Lead ID": "Op Lead ID",
"GET": "KRIJG",
"POST": "POSTE",
"PATCH": "BEKIJK",
"PUT": "PUT",
"DELETE": "VERWIJDEREN",
"HEAD": "HOOFD",
"New Lead Created": "Nieuwe Lead aangemaakt",
"New Contact Added": "Nieuwe contactpersoon toegevoegd",
"New Opportunity Added": "Nieuwe kans toegevoegd",
"Triggers when a new lead is created.": "Triggert wanneer een nieuwe lead wordt gemaakt.",
"Triggers when a new contact is created.": "Triggert wanneer een nieuw contact wordt aangemaakt.",
"Triggers when a new opportunity is created.": "Triggert wanneer een nieuwe kans wordt gecreëerd."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Automação de Vendas e Integração CRM para Fechar",
"Your Close CRM API key for authentication.": "Sua chave API Close CRM para autenticação.",
"Create Lead": "Criar Potencial",
"Create Contact": "Criar contato",
"Find Lead": "Encontrar Potencial",
"Create Opportunity": "Criar Oportunidade",
"Find Contact": "Localizar contato",
"Custom API Call": "Chamada de API personalizada",
"Creates a new lead.": "Cria um novo lead.",
"Creates a new contact.": "Cria um novo contato.",
"Search for leads with advanced filtering options": "Procurar por leads com opções de filtragem avançadas",
"Create a new opportunity.": "Crie uma nova oportunidade.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Procurar contatos por nome, email ou outro critério com filtragem avançada",
"Make a custom API call to a specific endpoint": "Faça uma chamada de API personalizada para um ponto de extremidade específico",
"Lead Name": "Nome do Potencial",
"URL": "URL:",
"Description": "Descrição",
"Contacts": "CONTATOS",
"Status": "Estado",
"Custom Fields": "Campos Personalizados",
"Lead": "Conduzir",
"Name": "Nome",
"Title": "Título",
"Office Phone": "Telefone Escritório",
"Mobile Phone": "Telefone Celular",
"Home Phone": "Telefone Residencial",
"Direct Phone": "Telefone Direto",
"Fax Phone": "Telefone do Fax",
"Other Phone": "Telefone Alternativo",
"Office Email": "E-mail do Escritório",
"Home Email": "E-mail Residencial",
"Direct Email": "Email Direto",
"Other Email": "E-mail Alternativo",
"Search Type": "Pesquisar Tipo",
"Search Query": "Consulta de Pesquisa",
"Match Type": "Tipo de correspondência",
"Opportunity Name": "Oportunidade Nome",
"Notes": "Observações",
"Confidence %": "% de confiança",
"Value": "Valor",
"Value Period": "Período do Valor",
"Contact ID": "ID do contato",
"User": "Usuário",
"Method": "Método",
"Headers": "Cabeçalhos",
"Query Parameters": "Parâmetros da consulta",
"Body": "Conteúdo",
"Response is Binary ?": "A resposta é binária ?",
"No Error on Failure": "Nenhum erro no Failure",
"Timeout (in seconds)": "Tempo limite (em segundos)",
"The name of the lead/company.": "Por favor, forneça um nome.",
"Array of contact details for this lead": "Array de detalhes de contato para este lead",
"A descriptive name for the opportunity.": "Um nome descritivo para a oportunidade.",
"Additional details about the opportunity.": "Detalhes adicionais sobre a oportunidade.",
"The probability of winning this opportunity (0-100).": "A probabilidade de ganhar esta oportunidade (0-100).",
"The period for the opportunity value.": "O período para o valor de oportunidade.",
"The ID of the contact associated with this opportunity.": "O ID do contato associado a esta oportunidade.",
"Authorization headers are injected automatically from your connection.": "Os cabeçalhos de autorização são inseridos automaticamente a partir da sua conexão.",
"Enable for files like PDFs, images, etc..": "Habilitar para arquivos como PDFs, imagens, etc..",
"By Name": "Por Nome",
"By Contact Email": "Por e-mail de contato",
"By Status": "Por status",
"Contains": "contém",
"Exact Match": "Partida exata",
"Starts With": "Começa com",
"Ends With": "Termina com",
"One-Time": "Única",
"Monthly": "Mensual",
"Annual": "Anual",
"By Email": "Por E-mail",
"By Phone": "Por telefone",
"By Lead ID": "Por ID do Lead",
"GET": "OBTER",
"POST": "POSTAR",
"PATCH": "COMPRAR",
"PUT": "COLOCAR",
"DELETE": "EXCLUIR",
"HEAD": "CABEÇA",
"New Lead Created": "Novo Potencial Criado",
"New Contact Added": "Novo contato adicionado",
"New Opportunity Added": "Nova Oportunidade Adicionado",
"Triggers when a new lead is created.": "Dispara quando um novo lead é criado.",
"Triggers when a new contact is created.": "Dispara quando um novo contato é criado.",
"Triggers when a new opportunity is created.": "Dispara quando uma nova oportunidade é criada."
}

View File

@@ -0,0 +1,85 @@
{
"Close": "Close",
"Sales automation and CRM integration for Close": "Автоматизация продаж и интеграция CRM для закрытия",
"Your Close CRM API key for authentication.": "Ваш закрытый API ключ для аутентификации.",
"Create Lead": "Создать предв. контакт",
"Create Contact": "Создать контакт",
"Find Lead": "Найти предв. контакт",
"Create Opportunity": "Создать сделку",
"Find Contact": "Найти контакт",
"Custom API Call": "Пользовательский вызов API",
"Creates a new lead.": "Создает нового лидера.",
"Creates a new contact.": "Создает новый контакт.",
"Search for leads with advanced filtering options": "Поиск проводов с расширенными опциями фильтрации",
"Create a new opportunity.": "Создать новую возможность.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Поиск контактов по имени, электронной почте или по другим критериям с расширенной фильтрацией",
"Make a custom API call to a specific endpoint": "Сделать пользовательский API вызов к определенной конечной точке",
"Lead Name": "Имя предварительного контакта",
"URL": "URL",
"Description": "Описание",
"Contacts": "Контакты",
"Status": "Статус",
"Custom Fields": "Пользовательские поля",
"Lead": "Предв. контакт",
"Name": "Наименование",
"Title": "Заголовок",
"Office Phone": "Тел. (раб.)",
"Mobile Phone": "Мобильный телефон",
"Home Phone": "Домашний телефон",
"Direct Phone": "Прямой телефон",
"Fax Phone": "Телефон факса",
"Other Phone": "Другой телефон",
"Office Email": "Почта офиса",
"Home Email": "Домашняя почта",
"Direct Email": "Прямая почта",
"Other Email": "Другой Email",
"Search Type": "Тип поиска",
"Search Query": "Поисковый запрос",
"Match Type": "Тип матча",
"Opportunity Name": "Название Сделки",
"Notes": "Примечания",
"Confidence %": "% доверия",
"Value": "Значение",
"Value Period": "Период стоимости",
"Contact ID": "ID контакта",
"User": "Пользователь",
"Method": "Метод",
"Headers": "Заголовки",
"Query Parameters": "Параметры запроса",
"Body": "Тело",
"No Error on Failure": "Нет ошибок при ошибке",
"Timeout (in seconds)": "Таймаут (в секундах)",
"The name of the lead/company.": "Название свинца/компании.",
"Array of contact details for this lead": "Массив контактных данных для этого провода",
"A descriptive name for the opportunity.": "Описательное название возможности.",
"Additional details about the opportunity.": "Дополнительная информация о возможности.",
"The probability of winning this opportunity (0-100).": "Вероятность выиграть эту возможность (0-100).",
"The period for the opportunity value.": "Период для стоимости сделки.",
"The ID of the contact associated with this opportunity.": "ID контакта, связанного с этой возможностью.",
"Authorization headers are injected automatically from your connection.": "Заголовки авторизации включаются автоматически из вашего соединения.",
"By Name": "По имени",
"By Contact Email": "По электронной почте",
"By Status": "По статусу",
"Contains": "Содержит",
"Exact Match": "Точное совпадение",
"Starts With": "Начинается с",
"Ends With": "Заканчивается с",
"One-Time": "Одноразовое",
"Monthly": "Ежемесячно",
"Annual": "Годовой",
"By Email": "По электронной почте",
"By Phone": "По телефону",
"By Lead ID": "По Lead ID",
"GET": "ПОЛУЧИТЬ",
"POST": "ПОСТ",
"PATCH": "ПАТЧ",
"PUT": "ПОКУПИТЬ",
"DELETE": "УДАЛИТЬ",
"HEAD": "HEAD",
"New Lead Created": "Создан новый контакт",
"New Contact Added": "Добавлен новый контакт",
"New Opportunity Added": "Новая сделка Добавлена",
"Triggers when a new lead is created.": "Триггеры при создании нового свинца.",
"Triggers when a new contact is created.": "Включает при создании нового контакта.",
"Triggers when a new opportunity is created.": "Триггеры при создании новой возможности."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Sales automation and CRM integration for Close",
"Your Close CRM API key for authentication.": "Your Close CRM API key for authentication.",
"Create Lead": "Create Lead",
"Create Contact": "Create Contact",
"Find Lead": "Find Lead",
"Create Opportunity": "Create Opportunity",
"Find Contact": "Find Contact",
"Custom API Call": "Custom API Call",
"Creates a new lead.": "Creates a new lead.",
"Creates a new contact.": "Creates a new contact.",
"Search for leads with advanced filtering options": "Search for leads with advanced filtering options",
"Create a new opportunity.": "Create a new opportunity.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Search for contacts by name, email, or other criteria with advanced filtering",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Lead Name": "Lead Name",
"URL": "URL",
"Description": "Description",
"Contacts": "Contacts",
"Status": "Status",
"Custom Fields": "Custom Fields",
"Lead": "Lead",
"Name": "Name",
"Title": "Title",
"Office Phone": "Office Phone",
"Mobile Phone": "Mobile Phone",
"Home Phone": "Home Phone",
"Direct Phone": "Direct Phone",
"Fax Phone": "Fax Phone",
"Other Phone": "Other Phone",
"Office Email": "Office Email",
"Home Email": "Home Email",
"Direct Email": "Direct Email",
"Other Email": "Other Email",
"Search Type": "Search Type",
"Search Query": "Search Query",
"Match Type": "Match Type",
"Opportunity Name": "Opportunity Name",
"Notes": "Notes",
"Confidence %": "Confidence %",
"Value": "Value",
"Value Period": "Value Period",
"Contact ID": "Contact ID",
"User": "User",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"The name of the lead/company.": "The name of the lead/company.",
"Array of contact details for this lead": "Array of contact details for this lead",
"A descriptive name for the opportunity.": "A descriptive name for the opportunity.",
"Additional details about the opportunity.": "Additional details about the opportunity.",
"The probability of winning this opportunity (0-100).": "The probability of winning this opportunity (0-100).",
"The period for the opportunity value.": "The period for the opportunity value.",
"The ID of the contact associated with this opportunity.": "The ID of the contact associated with this opportunity.",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"By Name": "By Name",
"By Contact Email": "By Contact Email",
"By Status": "By Status",
"Contains": "Contains",
"Exact Match": "Exact Match",
"Starts With": "Starts With",
"Ends With": "Ends With",
"One-Time": "One-Time",
"Monthly": "Monthly",
"Annual": "Annual",
"By Email": "By Email",
"By Phone": "By Phone",
"By Lead ID": "By Lead ID",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New Lead Created": "New Lead Created",
"New Contact Added": "New Contact Added",
"New Opportunity Added": "New Opportunity Added",
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
"Triggers when a new contact is created.": "Triggers when a new contact is created.",
"Triggers when a new opportunity is created.": "Triggers when a new opportunity is created."
}

View File

@@ -0,0 +1,85 @@
{
"Close": "Close",
"Sales automation and CRM integration for Close": "Sales automation and CRM integration for Close",
"Your Close CRM API key for authentication.": "Your Close CRM API key for authentication.",
"Create Lead": "Create Lead",
"Create Contact": "Create Contact",
"Find Lead": "Find Lead",
"Create Opportunity": "Create Opportunity",
"Find Contact": "Find Contact",
"Custom API Call": "Custom API Call",
"Creates a new lead.": "Creates a new lead.",
"Creates a new contact.": "Creates a new contact.",
"Search for leads with advanced filtering options": "Search for leads with advanced filtering options",
"Create a new opportunity.": "Create a new opportunity.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Search for contacts by name, email, or other criteria with advanced filtering",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Lead Name": "Lead Name",
"URL": "URL",
"Description": "Description",
"Contacts": "Contacts",
"Status": "Status",
"Custom Fields": "Custom Fields",
"Lead": "Lead",
"Name": "Name",
"Title": "Title",
"Office Phone": "Office Phone",
"Mobile Phone": "Mobile Phone",
"Home Phone": "Home Phone",
"Direct Phone": "Direct Phone",
"Fax Phone": "Fax Phone",
"Other Phone": "Other Phone",
"Office Email": "Office Email",
"Home Email": "Home Email",
"Direct Email": "Direct Email",
"Other Email": "Other Email",
"Search Type": "Search Type",
"Search Query": "Search Query",
"Match Type": "Match Type",
"Opportunity Name": "Opportunity Name",
"Notes": "Notes",
"Confidence %": "Confidence %",
"Value": "Value",
"Value Period": "Value Period",
"Contact ID": "Contact ID",
"User": "User",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"The name of the lead/company.": "The name of the lead/company.",
"Array of contact details for this lead": "Array of contact details for this lead",
"A descriptive name for the opportunity.": "A descriptive name for the opportunity.",
"Additional details about the opportunity.": "Additional details about the opportunity.",
"The probability of winning this opportunity (0-100).": "The probability of winning this opportunity (0-100).",
"The period for the opportunity value.": "The period for the opportunity value.",
"The ID of the contact associated with this opportunity.": "The ID of the contact associated with this opportunity.",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"By Name": "By Name",
"By Contact Email": "By Contact Email",
"By Status": "By Status",
"Contains": "Contains",
"Exact Match": "Exact Match",
"Starts With": "Starts With",
"Ends With": "Ends With",
"One-Time": "One-Time",
"Monthly": "Monthly",
"Annual": "Annual",
"By Email": "By Email",
"By Phone": "By Phone",
"By Lead ID": "By Lead ID",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New Lead Created": "New Lead Created",
"New Contact Added": "New Contact Added",
"New Opportunity Added": "New Opportunity Added",
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
"Triggers when a new contact is created.": "Triggers when a new contact is created.",
"Triggers when a new opportunity is created.": "Triggers when a new opportunity is created."
}

View File

@@ -0,0 +1,86 @@
{
"Sales automation and CRM integration for Close": "Sales automation and CRM integration for Close",
"Your Close CRM API key for authentication.": "Your Close CRM API key for authentication.",
"Create Lead": "Create Lead",
"Create Contact": "Create Contact",
"Find Lead": "Find Lead",
"Create Opportunity": "Create Opportunity",
"Find Contact": "Find Contact",
"Custom API Call": "自定义 API 呼叫",
"Creates a new lead.": "Creates a new lead.",
"Creates a new contact.": "Creates a new contact.",
"Search for leads with advanced filtering options": "Search for leads with advanced filtering options",
"Create a new opportunity.": "Create a new opportunity.",
"Search for contacts by name, email, or other criteria with advanced filtering": "Search for contacts by name, email, or other criteria with advanced filtering",
"Make a custom API call to a specific endpoint": "将一个自定义 API 调用到一个特定的终点",
"Lead Name": "Lead Name",
"URL": "URL",
"Description": "描述",
"Contacts": "Contacts",
"Status": "状态",
"Custom Fields": "Custom Fields",
"Lead": "Lead",
"Name": "名称",
"Title": "标题",
"Office Phone": "Office Phone",
"Mobile Phone": "Mobile Phone",
"Home Phone": "Home Phone",
"Direct Phone": "Direct Phone",
"Fax Phone": "Fax Phone",
"Other Phone": "Other Phone",
"Office Email": "Office Email",
"Home Email": "Home Email",
"Direct Email": "Direct Email",
"Other Email": "Other Email",
"Search Type": "Search Type",
"Search Query": "Search Query",
"Match Type": "Match Type",
"Opportunity Name": "Opportunity Name",
"Notes": "Notes",
"Confidence %": "Confidence %",
"Value": "值",
"Value Period": "Value Period",
"Contact ID": "Contact ID",
"User": "用户",
"Method": "方法",
"Headers": "信头",
"Query Parameters": "查询参数",
"Body": "正文内容",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "失败时没有错误",
"Timeout (in seconds)": "超时(秒)",
"The name of the lead/company.": "The name of the lead/company.",
"Array of contact details for this lead": "Array of contact details for this lead",
"A descriptive name for the opportunity.": "A descriptive name for the opportunity.",
"Additional details about the opportunity.": "Additional details about the opportunity.",
"The probability of winning this opportunity (0-100).": "The probability of winning this opportunity (0-100).",
"The period for the opportunity value.": "The period for the opportunity value.",
"The ID of the contact associated with this opportunity.": "The ID of the contact associated with this opportunity.",
"Authorization headers are injected automatically from your connection.": "授权头自动从您的连接中注入。",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"By Name": "By Name",
"By Contact Email": "By Contact Email",
"By Status": "By Status",
"Contains": "Contains",
"Exact Match": "精确匹配",
"Starts With": "Starts With",
"Ends With": "Ends With",
"One-Time": "One-Time",
"Monthly": "Monthly",
"Annual": "Annual",
"By Email": "By Email",
"By Phone": "By Phone",
"By Lead ID": "By Lead ID",
"GET": "获取",
"POST": "帖子",
"PATCH": "PATCH",
"PUT": "弹出",
"DELETE": "删除",
"HEAD": "黑色",
"New Lead Created": "New Lead Created",
"New Contact Added": "New Contact Added",
"New Opportunity Added": "New Opportunity Added",
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
"Triggers when a new contact is created.": "Triggers when a new contact is created.",
"Triggers when a new opportunity is created.": "Triggers when a new opportunity is created."
}

View File

@@ -0,0 +1,62 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import {
createCustomApiCallAction,
HttpMethod,
} from '@activepieces/pieces-common';
import { createLead } from './lib/actions/create-lead';
import { findLead } from './lib/actions/find-lead';
import { newLeadAdded } from './lib/triggers/new-lead-added';
import { createOpportunity } from './lib/actions/create-opportunity';
import { createContact } from './lib/actions/create-contact';
import { newContactAdded } from './lib/triggers/new-contact-added';
import { findContact } from './lib/actions/find-contact';
import { CLOSE_API_URL, closeApiCall } from './lib/common/client';
import { newOpportunityAdded } from './lib/triggers/new-opportunity';
export const closeAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: 'Your Close CRM API key for authentication.',
required: true,
validate: async ({ auth }) => {
try {
await closeApiCall({
accessToken: auth,
method: HttpMethod.GET,
resourceUri: '/me/',
});
return { valid: true };
} catch {
return {
valid: false,
error: 'Invalid API key.',
};
}
},
});
export const close = createPiece({
displayName: 'Close',
description: 'Sales automation and CRM integration for Close',
auth: closeAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/close.png',
authors: ['Ani-4x', 'kishanprmr'],
actions: [
createLead,
createContact,
findLead,
createOpportunity,
findContact,
createCustomApiCallAction({
baseUrl: () => CLOSE_API_URL,
auth: closeAuth,
authMapping: async (auth) => {
return {
Authorization: `Basic ${Buffer.from(`${auth}:`).toString('base64')}`,
};
},
}),
],
triggers: [newLeadAdded, newContactAdded, newOpportunityAdded],
});

View File

@@ -0,0 +1,140 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { CloseCRMContact } from '../common/types';
import { customFields, leadId } from '../common/props';
import { closeApiCall } from '../common/client';
export const createContact = createAction({
auth: closeAuth,
name: 'create_contact',
displayName: 'Create Contact',
description: 'Creates a new contact.',
props: {
lead_id: leadId(),
name: Property.ShortText({
displayName: 'Name',
required: true,
}),
title: Property.ShortText({
displayName: 'Title',
required: false,
}),
officePhone: Property.ShortText({
displayName: 'Office Phone',
required: false,
}),
mobilePhone: Property.ShortText({
displayName: 'Mobile Phone',
required: false,
}),
homePhone: Property.ShortText({
displayName: 'Home Phone',
required: false,
}),
directPhone: Property.ShortText({
displayName: 'Direct Phone',
required: false,
}),
faxPhone: Property.ShortText({
displayName: 'Fax Phone',
required: false,
}),
otherPhone: Property.ShortText({
displayName: 'Other Phone',
required: false,
}),
officeEmail: Property.ShortText({
displayName: 'Office Email',
required: false,
}),
homeEmail: Property.ShortText({
displayName: 'Home Email',
required: false,
}),
directEmail: Property.ShortText({
displayName: 'Direct Email',
required: false,
}),
otherEmail: Property.ShortText({
displayName: 'Other Email',
required: false,
}),
url: Property.ShortText({
displayName: 'URL',
required: false,
}),
customFields: customFields('contact'),
},
async run(context) {
const {
lead_id,
name,
title,
officeEmail,
officePhone,
otherEmail,
otherPhone,
mobilePhone,
homeEmail,
url,
homePhone,
directEmail,
directPhone,
faxPhone,
} = context.propsValue;
const customFields = context.propsValue.customFields ?? {};
const transformedCustomFields = Object.fromEntries(
Object.entries(customFields)
.filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0))
.map(([key, value]) => [`custom.${key}`, value]),
);
const payload: Partial<CloseCRMContact> = {
lead_id: lead_id as string,
title: title,
name: name,
...transformedCustomFields,
phones: [],
emails: [],
urls: [],
};
// Add emails if present
if (officeEmail) payload.emails?.push({ email: officeEmail.trim(), type: 'office' });
if (otherEmail) payload.emails?.push({ email: otherEmail.trim(), type: 'other' });
if (homeEmail) payload.emails?.push({ email: homeEmail.trim(), type: 'home' });
if (directEmail) payload.emails?.push({ email: directEmail.trim(), type: 'direct' });
// Add phones if present
if (officePhone) payload.phones?.push({ phone: officePhone.trim(), type: 'office' });
if (otherPhone) payload.phones?.push({ phone: otherPhone.trim(), type: 'other' });
if (mobilePhone) payload.phones?.push({ phone: mobilePhone.trim(), type: 'mobile' });
if (homePhone) payload.phones?.push({ phone: homePhone.trim(), type: 'home' });
if (directPhone) payload.phones?.push({ phone: directPhone.trim(), type: 'direct' });
if (faxPhone) payload.phones?.push({ phone: faxPhone.trim(), type: 'fax' });
if (url) payload.urls?.push({ url, type: 'url' });
try {
const response = await closeApiCall({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/contact/',
body: payload,
});
return response;
} catch (error: any) {
if (error.response?.status === 400) {
throw new Error(`Bad request: ${JSON.stringify(error.response.body)}`);
}
if (error.response?.status === 404) {
throw new Error(`Lead not found with ID: ${lead_id}`);
}
throw new Error(`Error creating contact: ${error.message}`);
}
},
});

View File

@@ -0,0 +1,177 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { closeAuth } from './../../index';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeApiCall } from '../common/client';
import { customFields, statusId } from '../common/props';
export const createLead = createAction({
auth: closeAuth,
name: 'create_lead',
displayName: 'Create Lead',
description: 'Creates a new lead.',
props: {
name: Property.ShortText({
displayName: 'Lead Name',
description: 'The name of the lead/company.',
required: true,
}),
url: Property.ShortText({
displayName: 'URL',
required: false,
}),
description: Property.LongText({
displayName: 'Description',
required: false,
}),
contacts: Property.Array({
displayName: 'Contacts',
description: 'Array of contact details for this lead',
required: false,
properties: {
name: Property.ShortText({
displayName: 'Contact Name',
required: true,
}),
title: Property.ShortText({
displayName: 'Contact Title',
required: false,
}),
officePhone: Property.ShortText({
displayName: 'Contact Office Phone',
required: false,
}),
mobilePhone: Property.ShortText({
displayName: 'Contact Mobile Phone',
required: false,
}),
homePhone: Property.ShortText({
displayName: 'Contact Home Phone',
required: false,
}),
directPhone: Property.ShortText({
displayName: 'Contact Direct Phone',
required: false,
}),
faxPhone: Property.ShortText({
displayName: 'Contact Fax Phone',
required: false,
}),
otherPhone: Property.ShortText({
displayName: 'Contact Other Phone',
required: false,
}),
officeEmail: Property.ShortText({
displayName: 'Contact Office Email',
required: false,
}),
homeEmail: Property.ShortText({
displayName: 'Contact Home Email',
required: false,
}),
directEmail: Property.ShortText({
displayName: 'Contact Direct Email',
required: false,
}),
otherEmail: Property.ShortText({
displayName: 'Contact Other Email',
required: false,
}),
url: Property.ShortText({
displayName: 'Contact URL',
required: false,
}),
},
}),
statusId: statusId('lead', false),
customFields: customFields('lead'),
},
async run(context) {
const { name, url, description, statusId } = context.propsValue;
const contacts = (context.propsValue.contacts as ContactInfo[]) ?? [];
const customFields = context.propsValue.customFields ?? {};
const transformedCustomFields = Object.fromEntries(
Object.entries(customFields)
.filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0))
.map(([key, value]) => [`custom.${key}`, value]),
);
const transformedContacts = contacts.map((contact) => {
const phoneTypes = [
'officePhone',
'mobilePhone',
'homePhone',
'directPhone',
'faxPhone',
'otherPhone',
] as const;
const emailTypes = ['officeEmail', 'homeEmail', 'directEmail', 'otherEmail'] as const;
const phones = phoneTypes
.filter((key) => contact[key]?.trim())
.map((key) => ({
type: key.replace('Phone', ''),
phone: contact[key]!.trim(),
}));
const emails = emailTypes
.filter((key) => contact[key]?.trim())
.map((key) => ({
type: key.replace('Email', ''),
email: contact[key]!.trim(),
}));
return {
name: contact.name?.trim(),
title: contact.title?.trim(),
phones: phones.length > 0 ? phones : undefined,
emails: emails.length > 0 ? emails : undefined,
urls: contact.url
? [
{
type: 'url',
url: contact.url,
},
]
: [],
};
});
const payload: Record<string, any> = {
name,
url,
description,
status_id: statusId,
contacts: transformedContacts,
...transformedCustomFields,
};
const response = await closeApiCall({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/lead/',
body: payload,
});
return response;
},
});
type ContactInfo = {
name?: string;
title?: string;
officePhone?: string;
mobilePhone?: string;
homePhone?: string;
directPhone?: string;
faxPhone?: string;
otherPhone?: string;
officeEmail?: string;
mobileEmail?: string;
homeEmail?: string;
directEmail?: string;
faxEmail?: string;
otherEmail?: string;
url?: string;
};

View File

@@ -0,0 +1,99 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { customFields, leadId, statusId, userId } from '../common/props';
import { closeApiCall } from '../common/client';
export const createOpportunity = createAction({
auth: closeAuth,
name: 'create_opportunity',
displayName: 'Create Opportunity',
description: 'Create a new opportunity.',
props: {
lead_id: leadId(),
status_id: statusId('opportunity', true),
name: Property.ShortText({
displayName: 'Opportunity Name',
description: 'A descriptive name for the opportunity.',
required: false,
}),
note: Property.LongText({
displayName: 'Notes',
description: 'Additional details about the opportunity.',
required: false,
}),
confidence: Property.Number({
displayName: 'Confidence %',
description: 'The probability of winning this opportunity (0-100).',
required: false,
}),
value: Property.Number({
displayName: 'Value',
required: false,
}),
value_period: Property.StaticDropdown({
displayName: 'Value Period',
description: 'The period for the opportunity value.',
required: false,
options: {
options: [
{ label: 'One-Time', value: 'one_time' },
{ label: 'Monthly', value: 'monthly' },
{ label: 'Annual', value: 'annual' },
],
},
defaultValue: 'one_time',
}),
contact_id: Property.ShortText({
displayName: 'Contact ID',
description: 'The ID of the contact associated with this opportunity.',
required: false,
}),
user_id: userId(),
custom_fields: customFields('opportunity'),
},
async run(context) {
const { lead_id, name, note, status_id, confidence, value, value_period, contact_id, user_id } =
context.propsValue;
const customFields = context.propsValue.custom_fields ?? {};
const transformedCustomFields = Object.fromEntries(
Object.entries(customFields)
.filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0))
.map(([key, value]) => [`custom.${key}`, value]),
);
const opportunityData = {
lead_id,
status_id,
name,
note,
confidence,
value,
value_period,
contact_id,
user_id,
...transformedCustomFields,
};
try {
const response = await closeApiCall({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/opportunity/',
body: opportunityData,
});
return response;
} catch (error: any) {
if (error.response?.status === 400) {
throw new Error(`Invalid request: ${JSON.stringify(error.response.body)}`);
}
if (error.response?.status === 404) {
throw new Error(`Lead or related resource not found`);
}
throw new Error(`Failed to create opportunity: ${error.message}`);
}
},
});

View File

@@ -0,0 +1,202 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { CloseCRMSearchQuery } from '../common/types';
import { closeApiCall } from '../common/client';
export const findContact = createAction({
auth: closeAuth,
name: 'find_contact',
displayName: 'Find Contact',
description: 'Search for contacts by name, email, or other criteria with advanced filtering',
props: {
search_type: Property.StaticDropdown({
displayName: 'Search Type',
required: true,
options: {
options: [
{ label: 'By Name', value: 'name' },
{ label: 'By Email', value: 'email' },
{ label: 'By Phone', value: 'phone' },
{ label: 'By Lead ID', value: 'lead_id' },
],
},
}),
search_query: Property.ShortText({
displayName: 'Search Query',
required: true,
}),
match_type: Property.StaticDropdown({
displayName: 'Match Type',
required: false,
options: {
options: [
{ label: 'Contains', value: 'contains' },
{ label: 'Exact Match', value: 'exact' },
{ label: 'Starts With', value: 'starts' },
{ label: 'Ends With', value: 'ends' },
],
},
defaultValue: 'contains',
}),
},
async run(context) {
const { search_type, search_query, match_type } = context.propsValue;
try {
// Build the search query
const searchQuery = buildSearchQuery({
search_type,
search_query,
match_type: match_type || 'contains',
include_fields: ['title', 'id', 'name', 'emails'],
});
let cursor: string | undefined;
const result = [];
do {
const response = await closeApiCall<{
cursor?: string;
data: Record<string, any>[];
}>({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/data/search/',
body: {
_limit: 100,
cursor,
...searchQuery,
},
});
const { data } = response;
if (!data || data.length === 0) break;
result.push(...data);
cursor = response.cursor;
} while (cursor);
return {
found: result.length > 0,
result,
};
} catch (error: any) {
if (error.response?.status === 400) {
throw new Error(`Invalid search query: ${error.response.body?.error || 'Unknown error'}`);
}
if (error.response?.status === 401) {
throw new Error('Authentication failed. Please check your API key.');
}
throw new Error(`Failed to search contacts: ${error.message}`);
}
},
});
// Helper function to build the search query
function buildSearchQuery(params: {
search_type: string;
search_query: string;
match_type: string;
include_fields: string[];
}): CloseCRMSearchQuery {
const { search_type, search_query, match_type, include_fields } = params;
const baseQuery = {
type: 'object_type',
object_type: 'contact',
};
let fieldCondition;
switch (search_type) {
case 'name':
fieldCondition = {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'contact',
field_name: 'name',
},
condition: {
type: 'text',
mode: 'full_words',
value: search_query,
},
};
break;
case 'email':
fieldCondition = {
type: 'has_related',
this_object_type: 'contact',
related_object_type: 'contact_email',
related_query: {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'contact_email',
field_name: 'email',
},
condition: {
type: 'text',
mode: 'phrase',
value: search_query,
},
},
};
break;
case 'phone':
fieldCondition = {
type: 'has_related',
this_object_type: 'contact',
related_object_type: 'contact_phone',
related_query: {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'contact_phone',
field_name: 'phone',
},
condition: {
type: 'text',
mode: 'phrase',
value: search_query,
},
},
};
break;
case 'lead_id':
fieldCondition = {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'contact',
field_name: 'lead_id',
},
condition: {
type: 'text',
mode: 'phrase', // Always exact match for IDs
value: search_query,
},
};
break;
default:
throw new Error(`Unsupported search type: ${search_type}`);
}
return {
query: {
type: 'and',
queries: [baseQuery, fieldCondition],
},
_fields: {
lead: ['id', 'name', 'status_label', 'contacts'],
contact: ['id', 'name', 'emails', 'phones'],
},
};
}

View File

@@ -0,0 +1,203 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { CloseCRMSearchQuery } from '../common/types';
import { closeApiCall } from '../common/client';
export const findLead = createAction({
auth: closeAuth,
name: 'find_lead',
displayName: 'Find Lead',
description: 'Search for leads with advanced filtering options',
props: {
search_type: Property.StaticDropdown({
displayName: 'Search Type',
required: true,
options: {
options: [
{ label: 'By Name', value: 'name' },
{ label: 'By Contact Email', value: 'contact_email' },
{ label: 'By Status', value: 'status' },
],
},
}),
search_query: Property.ShortText({
displayName: 'Search Query',
required: true,
}),
match_type: Property.StaticDropdown({
displayName: 'Match Type',
required: false,
options: {
options: [
{ label: 'Contains', value: 'contains' },
{ label: 'Exact Match', value: 'exact' },
{ label: 'Starts With', value: 'starts' },
{ label: 'Ends With', value: 'ends' },
],
},
defaultValue: 'contains',
}),
},
async run(context) {
const { search_type, search_query, match_type } = context.propsValue;
try {
// Build the search query
const searchQuery = buildLeadSearchQuery({
search_type,
search_query,
match_type: match_type || 'contains',
});
let cursor: string | undefined;
const result = [];
do {
const response = await closeApiCall<{
cursor?: string;
data: Record<string, any>[];
}>({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/data/search/',
body: {
_limit: 100,
cursor,
...searchQuery,
},
});
const { data } = response;
if (!data || data.length === 0) break;
result.push(...data);
cursor = response.cursor;
} while (cursor);
return {
found: result.length > 0,
result,
};
} catch (error: any) {
if (error.response?.status === 400) {
throw new Error(`Invalid search query: ${error.response.body?.error || 'Unknown error'}`);
}
if (error.response?.status === 401) {
throw new Error('Authentication failed. Please check your API key.');
}
if (error.response?.status === 404) {
throw new Error('No leads found matching your criteria.');
}
throw new Error(`Failed to search leads: ${error.message}`);
}
},
});
// Helper function to build the lead search query
function buildLeadSearchQuery(params: {
search_type: string;
search_query: string;
match_type: string;
custom_field_name?: string;
}): CloseCRMSearchQuery {
const { search_type, search_query, match_type, custom_field_name } = params;
const baseQuery = {
type: 'object_type',
object_type: 'lead',
};
let fieldCondition;
switch (search_type) {
case 'name':
fieldCondition = {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'lead',
field_name: 'name',
},
condition: {
type: 'text',
mode: 'full_words',
value: search_query,
},
};
break;
case 'contact_email':
fieldCondition = {
type: 'has_related',
this_object_type: 'lead',
related_object_type: 'contact',
related_query: {
type: 'has_related',
this_object_type: 'contact',
related_object_type: 'contact_email',
related_query: {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'contact_email',
field_name: 'email',
},
condition: {
type: 'text',
mode: 'phrase',
value: search_query,
},
},
},
};
break;
case 'status':
fieldCondition = {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'lead',
field_name: 'status_label',
},
condition: {
type: 'text',
mode: 'phrase',
value: search_query,
},
};
break;
case 'custom_field':
fieldCondition = {
type: 'field_condition',
field: {
type: 'regular_field',
object_type: 'lead',
field_name: custom_field_name!,
},
condition: {
type: 'text',
mode: 'full_words',
value: search_query,
},
};
break;
default:
throw new Error(`Unsupported search type: ${search_type}`);
}
return {
query: {
type: 'and',
queries: [baseQuery, fieldCondition],
},
_fields: {
lead: ['id', 'name', 'status_label', 'contacts'],
contact: ['id', 'name', 'emails', 'phones'],
},
};
}

View File

@@ -0,0 +1,81 @@
import {
AuthenticationType,
httpClient,
HttpMessageBody,
HttpMethod,
HttpRequest,
QueryParams,
} from '@activepieces/pieces-common';
export type CloseApiCallParams = {
accessToken: string;
method: HttpMethod;
resourceUri: string;
query?: Record<string, string | number | string[] | undefined>;
body?: any;
};
export const CLOSE_API_URL = 'https://api.close.com/api/v1';
export async function closeApiCall<T extends HttpMessageBody>({
accessToken,
method,
resourceUri,
query,
body,
}: CloseApiCallParams): Promise<T> {
const qs: QueryParams = {};
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== null && value !== undefined) {
qs[key] = String(value);
}
}
}
const request: HttpRequest = {
method,
url: CLOSE_API_URL + resourceUri,
authentication: {
type: AuthenticationType.BASIC,
username: accessToken,
password: '',
},
queryParams: qs,
body,
};
const response = await httpClient.sendRequest<T>(request);
return response.body;
}
export async function closePaginatedApiCall<T extends HttpMessageBody>({
accessToken,
method,
resourceUri,
query,
body,
}: CloseApiCallParams): Promise<T[]> {
const resultData: T[] = [];
const limit = 100;
let skip = 0;
let hasMore = true;
do {
const response = await closeApiCall<{ data: T[]; has_more: boolean }>({
accessToken,
method,
resourceUri,
query: { ...query, _limit: limit, _skip: skip },
body,
});
const { data, has_more } = response;
if (!data || data.length === 0) break;
resultData.push(...data);
hasMore = has_more;
skip += limit;
} while (hasMore);
return resultData;
}

View File

@@ -0,0 +1,209 @@
import { DynamicPropsValue, Property } from '@activepieces/pieces-framework';
import { closePaginatedApiCall } from './client';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../..';
export const customFields = (objectType: string) =>
Property.DynamicProperties({
displayName: 'Custom Fields',
refreshers: [],
required: false,
auth: closeAuth,
props: async ({ auth }) => {
if (!auth) return {};
const fields: DynamicPropsValue = {};
const response = await closePaginatedApiCall<{
id: string;
type: string;
choices?: string[];
accepts_multiple_values: boolean;
name: string;
}>({
accessToken: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/custom_field/${objectType}/`,
});
for (const field of response) {
switch (field.type) {
case 'number':
fields[field.id] = Property.Number({
displayName: field.name,
required: false,
});
break;
case 'text':
fields[field.id] = Property.ShortText({
displayName: field.name,
required: false,
});
break;
case 'textarea':
fields[field.id] = Property.LongText({
displayName: field.name,
required: false,
});
break;
case 'date':
case 'datetime':
fields[field.id] = Property.DateTime({
displayName: field.name,
required: false,
});
break;
case 'choices': {
const fieldType = field.accepts_multiple_values
? Property.StaticMultiSelectDropdown
: Property.StaticDropdown;
fields[field.id] = fieldType({
displayName: field.name,
required: false,
options: {
disabled: false,
options: field.choices
? field.choices.map((choice) => ({
label: choice,
value: choice,
}))
: [],
},
});
break;
}
case 'contact':
case 'user': {
const fieldType = field.accepts_multiple_values ? Property.Array : Property.ShortText;
fields[field.id] = fieldType({
displayName: field.name,
required: false,
description: `Provide ${field.type} ID.`,
});
break;
}
default:
break;
}
}
return fields;
},
});
export const statusId = (objectType: string, required = false) =>
Property.Dropdown({
auth: closeAuth,
displayName: 'Status',
refreshers: [],
required,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
const response = await closePaginatedApiCall<{
id: string;
label: string;
}>({
accessToken: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/status/${objectType}/`,
});
return {
disabled: false,
options: response.map((status) => ({
label: status.label,
value: status.id,
})),
};
},
});
export const leadId = (required = false) =>
Property.Dropdown({
auth: closeAuth,
displayName: 'Lead',
required,
refreshers: ['auth'],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please authenticate first.',
};
}
try {
const response = await closePaginatedApiCall<{
id: string;
name: string;
}>({
accessToken: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/lead/?_fields=id,name',
});
return {
disabled: false,
options: response.map((lead) => ({
label: lead.name,
value: lead.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Could not fetch leads. Check your connection.',
};
}
},
});
export const userId = (required = false) =>
Property.Dropdown({
auth: closeAuth,
displayName: 'User',
required,
refreshers: ['auth'],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please authenticate first.',
};
}
try {
const response = await closePaginatedApiCall<{
id: string;
email: string;
}>({
accessToken: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/user/?_fields=id,email',
});
return {
disabled: false,
options: response.map((user) => ({
label: user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Could not fetch uers. Check your connection.',
};
}
},
});

View File

@@ -0,0 +1,194 @@
export interface CloseCRMLead {
id: string;
contacts?: {
name: string;
emails?: { email: string }[];
phones?: { phone: string }[];
}[];
[key: string]: unknown; // For custom fields
}
export interface CloseCRMClient {
post(endpoint: string, data: unknown): Promise<{ data: unknown }>;
// Add other methods as needed
}
export interface CloseCRMEmailActivity {
lead_id: string;
direction: 'outgoing' | 'incoming';
note: string;
date_created: string;
_type: 'email';
email: {
subject: string;
body: string;
sender?: string;
to: Array<{
email: string;
name?: string;
}>;
attachments?: Array<{
name: string;
url: string;
size?: number;
}>;
};
}
export interface CloseCRMLeadWebhookPayload {
subscription_id: string;
event: {
id: string;
action: 'created' | 'updated' | 'deleted';
object_type: 'lead';
object_id: string;
date_created: string;
data: {
id: string;
name: string;
status_label?: string;
status_id?: string;
contacts?: Array<string | ContactDetails>;
date_created: string;
date_updated: string;
};
};
}
interface ContactDetails {
id: string;
name: string;
emails?: Array<{ email: string; type?: string }>;
phones?: Array<{ phone: string; type?: string }>;
[key: string]: unknown;
}
//Contact name
export interface CloseCRMContact {
lead_id: string;
id: string;
name: string;
title?: string;
emails?: {
email: string;
type?: 'direct' | 'home' | 'other' | 'office';
}[];
phones?: {
phone: string;
type?: 'mobile' | 'office' | 'home' | 'fax' | 'other' | 'direct';
}[];
urls?: {
url: string;
type?: 'website' | 'linkedin' | 'twitter' | 'other' | 'url';
}[];
[key: string]: unknown; // For custom fields
}
//contact search query
export interface CloseCRMSearchQuery {
query: {
type: string;
queries: any[];
};
_fields: {
contact: string[];
lead: string[];
};
}
export interface CloseCRMSearchQuery {
query: {
type: string;
queries: any[];
};
}
//NEW-CONTACT-ADDED
export interface CloseCRMContactWebhookPayload {
subscription_id: string;
event: {
id: string;
action: 'created' | 'updated' | 'deleted';
object_type: 'contact';
object_id: string;
lead_id: string;
date_created: string;
data: {
id: string;
name: string;
title?: string;
lead_id: string;
emails?: Array<{ email: string; type?: string }>;
phones?: Array<{ phone: string; type?: string }>;
date_created: string;
date_updated: string;
};
};
}
//find opportunity
export interface CloseCRMOpportunity {
lead_id: string;
lead_name?: string;
status_id?: string;
status_label?: string;
status_type?: 'active' | 'won' | 'lost' | 'archived';
pipeline_id?: string;
pipeline_name?: string;
user_id?: string;
user_name?: string;
contact_id?: string;
value?: number;
value_period?: 'one_time' | 'monthly' | 'annual';
value_formatted?: string;
expected_value?: number;
annualized_value?: number;
annualized_expected_value?: number;
confidence?: number;
note?: string;
date_created?: string;
date_updated?: string;
date_won?: string;
}
//opportunity-status changed
export interface CloseCRMOpportunityWebhookPayload {
subscription_id: string;
event: {
id: string;
action: 'created' | 'updated' | 'deleted';
object_type: 'opportunity';
object_id: string;
lead_id: string;
payload_id: string;
date_created: string;
changed_fields: string[];
previous_data?: {
status_type?: string;
status_label?: string;
status_id?: string;
value?: number;
confidence?: number;
};
data: {
id: string;
lead_id: string;
status_type: 'active' | 'won' | 'lost' | 'archived';
status_label: string;
status_id: string;
value?: number;
value_currency?: string;
value_formatted?: string;
contact_id?: string;
contact_name?: string;
lead_name?: string;
date_won?: string;
date_lost?: string;
confidence?: number;
date_created: string;
date_updated: string;
}
}
}

View File

@@ -0,0 +1,28 @@
import crypto from 'crypto';
export const verifySignature = (
signatureKey?: string, // hex-encoded key
timestamp?: string, // 'close-sig-timestamp' header
rawBody?: any, // raw body as string
signatureHash?: string, // 'close-sig-hash' header
): boolean => {
if (!signatureKey || !timestamp || !rawBody || !signatureHash) {
return false;
}
try {
const dataToHmac = timestamp + rawBody;
const generatedHash = crypto
.createHmac('sha256', Buffer.from(signatureKey, 'hex'))
.update(dataToHmac, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(generatedHash, 'hex'),
Buffer.from(signatureHash, 'hex'),
);
} catch (error) {
return false;
}
};

View File

@@ -0,0 +1,132 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { CloseCRMContactWebhookPayload } from '../common/types';
import { closeApiCall } from '../common/client';
import { verifySignature } from './helpers';
const TRIGGER_KEY = 'new-contact-trigger';
export const newContactAdded = createTrigger({
auth: closeAuth,
name: 'new_contact_added',
displayName: 'New Contact Added',
description: 'Triggers when a new contact is created.',
type: TriggerStrategy.WEBHOOK,
props: {},
async onEnable(context) {
const response = await closeApiCall<{ id: string; signature_key: string }>({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/webhook/',
body: {
url: context.webhookUrl,
events: [
{
object_type: 'contact',
action: 'created',
},
],
},
});
const { id, signature_key: signatureKey } = response;
await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, {
id,
signatureKey,
});
},
async onDisable(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
if (triggerData?.id) {
await closeApiCall({
method: HttpMethod.DELETE,
accessToken: context.auth.secret_text,
resourceUri: `/webhook/${triggerData.id}`,
});
}
await context.store.delete(TRIGGER_KEY);
},
async run(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
const signatureKey = triggerData?.signatureKey;
const signatureHash = context.payload.headers['close-sig-hash'];
const timestamp = context.payload.headers['close-sig-timestamp'];
const rawBody = context.payload.rawBody;
if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) {
return [];
}
const payload = context.payload.body as CloseCRMContactWebhookPayload;
// Verify this is a lead creation event
if (payload.event.object_type !== 'contact' || payload.event.action !== 'created') {
return [];
}
const contact = await closeApiCall({
accessToken: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/contact/${payload.event.data.id}/`,
});
return [contact];
},
sampleData: {
created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
date_created: '2025-05-30T15:29:16.048000+00:00',
date_updated: '2025-05-30T15:29:16.048000+00:00',
display_name: 'John Doe',
emails: [
{
email: 'johndoe@gmail.com',
is_unsubscribed: false,
type: 'office',
},
{
email: 'johndoe@gmail.com',
is_unsubscribed: false,
type: 'direct',
},
],
id: 'cont_SNLEuIxMqrgu23m1D63rnE84ivTYlXm3pPRJXHWhGqn',
integration_links: [
{
name: 'LinkedIn Search',
url: 'https://www.linkedin.com/search/results/people/?keywords=JohnDoe',
},
],
lead_id: 'lead_Tn9KvxpJ2InYrwOb81TIoOuAoEchLPFJS72i0xMK2vj',
name: 'John Doe',
organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl',
phones: [
{
country: 'IN',
phone: '',
phone_formatted: '+911541',
type: 'mobile',
},
],
title: 'Test',
updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
urls: [
{
type: 'url',
url: 'https://www.github.com',
},
],
},
});

View File

@@ -0,0 +1,155 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../../';
import { CloseCRMLeadWebhookPayload } from '../common/types';
import { closeApiCall } from '../common/client';
import { verifySignature } from './helpers';
const TRIGGER_KEY = 'new-lead-trigger';
export const newLeadAdded = createTrigger({
auth: closeAuth,
name: 'new_lead_created',
displayName: 'New Lead Created',
description: 'Triggers when a new lead is created.',
type: TriggerStrategy.WEBHOOK,
props: {},
async onEnable(context) {
const response = await closeApiCall<{ id: string; signature_key: string }>({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/webhook/',
body: {
url: context.webhookUrl,
events: [
{
object_type: 'lead',
action: 'created',
},
],
},
});
const { id, signature_key: signatureKey } = response;
await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, {
id,
signatureKey,
});
},
async onDisable(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
if (triggerData?.id) {
await closeApiCall({
method: HttpMethod.DELETE,
accessToken: context.auth.secret_text,
resourceUri: `/webhook/${triggerData.id}`,
});
}
await context.store.delete(TRIGGER_KEY);
},
async run(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
const signatureKey = triggerData?.signatureKey;
const signatureHash = context.payload.headers['close-sig-hash'];
const timestamp = context.payload.headers['close-sig-timestamp'];
const rawBody = context.payload.rawBody;
if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) {
return [];
}
const payload = context.payload.body as CloseCRMLeadWebhookPayload;
// Verify this is a lead creation event
if (payload.event.object_type !== 'lead' || payload.event.action !== 'created') {
return [];
}
const lead = await closeApiCall({
accessToken: context.auth.secret_text ,
method: HttpMethod.GET,
resourceUri: `/lead/${payload.event.data.id}/`,
});
return [lead];
},
sampleData: {
addresses: [],
contacts: [
{
created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
date_created: '2025-05-30T15:30:40.148000+00:00',
date_updated: '2025-05-30T15:30:40.148000+00:00',
display_name: 'John Doe',
emails: [
{
email: 'johndoe@gmail.com',
is_unsubscribed: false,
type: 'office',
},
],
id: 'cont_p9jX6fiJ0AryAx06CwT9SY8OyA9PX29zmwXBw9ct3TR',
lead_id: 'lead_fy3zdUuEFQ1WJEi846CGSvpePgzqm0P6XTPRDtSBFTC',
name: 'John Doe',
organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl',
phones: [
{
country: 'IN',
phone: '+91754',
phone_formatted: '+91754',
type: 'office',
},
],
title: 'SDE',
updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
urls: [
{
type: 'url',
url: 'https://www.github.com',
},
],
},
],
created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
created_by_name: 'John Doe',
custom: {
'Current Vendor/Software': 'Close',
'Custom Field': 'test',
Industry: 'Healthcare',
date: '2025-05-30',
datetime: '2025-05-30T00:00:00+00:00',
},
date_created: '2025-05-30T15:30:40.122000+00:00',
date_updated: '2025-05-30T15:35:33.131000+00:00',
description: 'TESTING',
display_name: 'John Doe',
id: 'lead_fy3zdUuEFQ1WJEi846CGSvpePgzqm0P6XTPRDtSBFTC',
integration_links: [
{
name: 'Google Search',
url: 'https://google.com/search?q=HEllo%20KNOW',
},
],
name: 'John Doe',
opportunities: [],
organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl',
status_id: 'stat_piNPXI7AsUxHHwhPJKdCTiQtZsRP96HGb088FzJCgEJ',
status_label: 'Potential',
tasks: [],
updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
updated_by_name: 'John Doe',
url: 'https://www.github.com',
},
});

View File

@@ -0,0 +1,131 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { closeAuth } from '../..';
import { CloseCRMOpportunityWebhookPayload } from '../common/types';
import { closeApiCall } from '../common/client';
import { verifySignature } from './helpers';
const TRIGGER_KEY = 'new-opportunity-trigger';
export const newOpportunityAdded = createTrigger({
auth: closeAuth,
name: 'new_opportunity_added',
displayName: 'New Opportunity Added',
description: 'Triggers when a new opportunity is created.',
type: TriggerStrategy.WEBHOOK,
props: {},
async onEnable(context) {
const response = await closeApiCall<{ id: string; signature_key: string }>({
accessToken: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/webhook/',
body: {
url: context.webhookUrl,
events: [
{
object_type: 'opportunity',
action: 'created',
},
],
},
});
const { id, signature_key: signatureKey } = response;
await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, {
id,
signatureKey,
});
},
async onDisable(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
if (triggerData?.id) {
await closeApiCall({
method: HttpMethod.DELETE,
accessToken: context.auth.secret_text,
resourceUri: `/webhook/${triggerData.id}`,
});
}
await context.store.delete(TRIGGER_KEY);
},
async run(context) {
const triggerData = await context.store.get<{
id: string;
signatureKey: string;
}>(TRIGGER_KEY);
const signatureKey = triggerData?.signatureKey;
const signatureHash = context.payload.headers['close-sig-hash'];
const timestamp = context.payload.headers['close-sig-timestamp'];
const rawBody = context.payload.rawBody;
if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) {
return [];
}
const payload = context.payload.body as CloseCRMOpportunityWebhookPayload;
// Verify this is a lead creation event
if (payload.event.object_type !== 'opportunity' || payload.event.action !== 'created') {
return [];
}
const opportunity = await closeApiCall({
accessToken: context.auth.secret_text ,
method: HttpMethod.GET,
resourceUri: `/opportunity/${payload.event.data.id}/`,
});
return [opportunity];
},
sampleData: {
annualized_expected_value: 500,
annualized_value: 1000,
attachments: [],
confidence: 50,
contact_id: null,
contact_name: null,
created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
created_by_name: 'john doe',
'custom.cf_cJd1lRfCdIWTgJGLQ84lQZXiGw7ufhdXA2F907z9Mk8': 1,
'custom.cf_gOgoyjtw8iShE434uBERAEcR6wFoacUtlVPWcPA2EXS': 'Basic',
'custom.cf_lHRMHDYJVogylPbe6v0DCGcU7kMqz2RJUYnAhntKOOn': 10,
'custom.cf_usm3RdC8hm7Pb6pYo9M8lKCN1h06Edij9OdzHphwAkM': [
'Premium support',
'Professional services',
'Subscription',
],
date_created: '2025-05-30T15:53:52.829000+00:00',
date_lost: null,
date_updated: '2025-05-30T15:53:52.829000+00:00',
date_won: null,
expected_value: 500,
id: 'oppo_NkuyMMFjRDaY5mYQ7bywfpOmiHWPQ0h02bxJoZGwTOS',
integration_links: [],
lead_id: 'lead_Tn9KvxpJ2InYrwOb81TIoOuAoEchLPFJS72i0xMK2vj',
lead_name: 'TEST LEAD',
note: 'Test',
organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl',
pipeline_id: 'pipe_2kHJXqFpYKmONEWPQijcit',
pipeline_name: 'Sales',
status_display_name: 'Demo Completed',
status_id: 'stat_uqaIpeqabvBXW32bpLZkcD7mhTRvCXqEF0oGQDvOKMG',
status_label: 'Demo Completed',
status_type: 'active',
updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
updated_by_name: 'john doe',
user_id: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN',
user_name: 'john doe',
value: 1000,
value_currency: 'USD',
value_formatted: '$10',
value_period: 'one_time',
},
});