Skip to main content

# Integrações - Krayin CRM v2.1.6

Guia de integração com N8N, WhatsApp (Evolution API) e Frontend Operaes.


📋 Índice

  1. N8N Integration
  2. WhatsApp (Evolution API)
  3. Frontend Central Memude
  4. Outras Integrações

1. N8N Integration

Visão Geral

N8N é uma plataforma de automação que conecta ao Krayin via HTTP Request nodes.

┌─────────────┐     Webhook      ┌─────────────┐     API      ┌─────────────┐
│   Trigger   │ ───────────────► │     N8N     │ ───────────► │   Krayin    │
│  (WhatsApp) │                  │  Workflow   │              │     CRM     │
└─────────────┘                  └─────────────┘              └─────────────┘

Configuração Inicial

Passo 1: Obter Token do Krayin

curl -X POST "https://crm.memudecore.com.br/api/v1/login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=admin@memudecore.com.br&password=sua_senha&device_name=N8N"

Salve o token retornado.

Passo 2: Criar Credential no N8N

  1. Acesse N8N → Settings → Credentials
  2. Clique Add Credential
  3. Selecione Header Auth
  4. Configure:
    • Name: Krayin API
    • Header Name: Authorization
    • Header Value: Bearer {seu_token}

Workflow: Criar Lead de Formulário

┌─────────────────────────────────────────────────────────────────┐
│                         N8N WORKFLOW                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [Webhook Trigger] → [Validate Data] → [HTTP Request] → [Done] │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Node 1: Webhook Trigger

{
  "httpMethod": "POST",
  "path": "form-to-krayin",
  "responseMode": "onReceived"
}

Node 2: Set Node (Transformar Dados)

return {
  title: $json.body.empresa || $json.body.nome,
  source_id: 1,
  lead_type_id: 1,
  sales_owner_id: 1,
  value: parseFloat($json.body.valor) || 0,
  description: $json.body.mensagem || '',
  person_data: {
    name: $json.body.nome,
    email: $json.body.email,
    phone: $json.body.telefone
  }
}

Node 3: HTTP Request (Criar Contato)

{
  "method": "POST",
  "url": "https://crm.memudecore.com.br/api/v1/contacts/persons",
  "authentication": "predefinedCredentialType",
  "nodeCredentialType": "httpHeaderAuth",
  "sendHeaders": true,
  "headerParameters": {
    "Content-Type": "application/json"
  },
  "sendBody": true,
  "bodyParameters": {
    "name": "={{ $json.person_data.name }}",
    "emails": [{"value": "={{ $json.person_data.email }}", "label": "work"}],
    "contact_numbers": [{"value": "={{ $json.person_data.phone }}", "label": "work"}]
  }
}

Node 4: HTTP Request (Criar Lead)

{
  "method": "POST",
  "url": "https://crm.memudecore.com.br/api/v1/leads",
  "authentication": "predefinedCredentialType",
  "nodeCredentialType": "httpHeaderAuth",
  "sendBody": true,
  "bodyParameters": {
    "title": "={{ $('Set').item.json.title }}",
    "source_id": 1,
    "lead_type_id": 1,
    "sales_owner_id": 1,
    "value": "={{ $('Set').item.json.value }}",
    "contact_person_id": "={{ $json.data.id }}",
    "description": "={{ $('Set').item.json.description }}"
  }
}

Workflow: Sync Krayin → Google Sheets

// Node: Schedule Trigger (diário às 8h)

// Node: HTTP Request - Buscar leads do dia anterior
{
  "method": "GET",
  "url": "https://crm.memudecore.com.br/api/v1/leads?created_after={{ $now.minus(1, 'day').toISO() }}"
}

// Node: Google Sheets - Append rows
// Mapear campos do lead para colunas da planilha

2. WhatsApp (Evolution API)

Arquitetura

┌─────────────┐     Mensagem     ┌─────────────┐     Webhook     ┌─────────────┐
│  WhatsApp   │ ───────────────► │  Evolution  │ ───────────────► │     N8N     │
│   (User)    │                  │     API     │                  │             │
└─────────────┘                  └─────────────┘                  └──────┬──────┘
                                                                         │
                                                                         │ API
                                                                         ▼
                                                                  ┌─────────────┐
                                                                  │   Krayin    │
                                                                  │     CRM     │
                                                                  └─────────────┘

Configurar Evolution API

Passo 1: Criar Instância WhatsApp

curl -X POST "https://evolution.memudecore.com.br/instance/create" \
  -H "Authorization: Bearer SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instanceName": "krayin-whatsapp",
    "qrcode": true,
    "integration": "WHATSAPP-BAILEYS"
  }'

Passo 2: Configurar Webhook

curl -X POST "https://evolution.memudecore.com.br/webhook/set/krayin-whatsapp" \
  -H "Authorization: Bearer SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook": {
      "enabled": true,
      "url": "https://n8n.memudecore.com.br/webhook/whatsapp-krayin",
      "events": [
        "MESSAGES_UPSERT",
        "CONNECTION_UPDATE"
      ]
    }
  }'

Workflow N8N: WhatsApp → Lead

Payload do Evolution API

{
  "event": "MESSAGES_UPSERT",
  "data": {
    "key": {
      "remoteJid": "5511987654321@s.whatsapp.net",
      "fromMe": false,
      "id": "ABC123"
    },
    "message": {
      "conversation": "Olá, tenho interesse no produto X"
    },
    "pushName": "João Silva"
  }
}

Node: Code (Extrair Dados)

const jid = $json.data.key.remoteJid;
const phone = '+' + jid.split('@')[0];
const name = $json.data.pushName || 'WhatsApp Contact';
const message = $json.data.message.conversation ||
                $json.data.message.extendedTextMessage?.text ||
                '[Mídia]';

return {
  phone: phone,
  name: name,
  message: message,
  source: 'whatsapp',
  jid: jid
};

Node: HTTP Request (Buscar Contato Existente)

{
  "method": "GET",
  "url": "https://crm.memudecore.com.br/api/v1/contacts/persons",
  "qs": {
    "search": "={{ $json.phone }}"
  }
}

Node: IF (Contato Existe?)

Condition: {{ $json.data.length > 0 }}
True → Usar ID existente
False → Criar novo contato

Node: HTTP Request (Criar Lead)

{
  "method": "POST",
  "url": "https://crm.memudecore.com.br/api/v1/leads",
  "body": {
    "title": "WhatsApp - {{ $('Code').item.json.name }}",
    "source_id": 5,
    "lead_type_id": 1,
    "sales_owner_id": 1,
    "contact_person_id": "{{ $json.person_id }}",
    "description": "Mensagem: {{ $('Code').item.json.message }}",
    "tags": ["whatsapp"]
  }
}

Enviar Resposta pelo WhatsApp

# Via Evolution API
curl -X POST "https://evolution.memudecore.com.br/message/sendText/krayin-whatsapp" \
  -H "Authorization: Bearer SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "5511987654321",
    "text": "Olá! Recebemos sua mensagem. Em breve um consultor entrará em contato."
  }'

3. Frontend Operaes (TypeScript)

Configuração do Cliente API

api/krayin.ts

import axios, { AxiosInstance, AxiosError } from 'axios';

interface KrayinConfig {
  baseURL: string;
  token?: string;
}

interface LoginResponse {
  token: string;
  message: string;
}

interface Lead {
  id: number;
  title: string;
  source_id: number;
  lead_type_id: number;
  sales_owner_id: number;
  status_id: number;
  value: number;
  expected_close_date?: string;
  description?: string;
  contact_person?: Person;
  organization?: Organization;
  created_at: string;
  updated_at: string;
}

interface Person {
  id: number;
  name: string;
  emails: Array<{ value: string; label: string }>;
  contact_numbers: Array<{ value: string; label: string }>;
}

interface Organization {
  id: number;
  name: string;
}

interface PaginatedResponse<T> {
  data: T[];
  links: {
    first: string;
    last: string;
    prev: string | null;
    next: string | null;
  };
  meta: {
    current_page: number;
    from: number;
    last_page: number;
    per_page: number;
    to: number;
    total: number;
  };
}

class KrayinAPI {
  private client: AxiosInstance;
  private token: string | null = null;

  constructor(config: KrayinConfig) {
    this.client = axios.create({
      baseURL: config.baseURL,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    });

    if (config.token) {
      this.setToken(config.token);
    }

    // Interceptor para erros
    this.client.interceptors.response.use(
      response => response,
      (error: AxiosError) => {
        if (error.response?.status === 401) {
          // Token expirado - redirecionar para login
          this.token = null;
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
  }

  setToken(token: string) {
    this.token = token;
    this.client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  }

  // ==================== AUTH ====================

  async login(email: string, password: string): Promise<string> {
    const params = new URLSearchParams();
    params.append('email', email);
    params.append('password', password);
    params.append('device_name', 'Operaes');

    const response = await this.client.post<LoginResponse>('/login', params, {
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    });

    this.setToken(response.data.token);
    return response.data.token;
  }

  // ==================== LEADS ====================

  async getLeads(params?: {
    page?: number;
    per_page?: number;
    search?: string;
    source_id?: number;
    status_id?: number;
    sales_owner_id?: number;
  }): Promise<PaginatedResponse<Lead>> {
    const response = await this.client.get<PaginatedResponse<Lead>>('/leads', { params });
    return response.data;
  }

  async getLead(id: number): Promise<Lead> {
    const response = await this.client.get<{ data: Lead }>(`/leads/${id}`);
    return response.data.data;
  }

  async createLead(data: Partial<Lead>): Promise<Lead> {
    const response = await this.client.post<{ data: Lead }>('/leads', data);
    return response.data.data;
  }

  async updateLead(id: number, data: Partial<Lead>): Promise<Lead> {
    const response = await this.client.put<{ data: Lead }>(`/leads/${id}`, data);
    return response.data.data;
  }

  async deleteLead(id: number): Promise<void> {
    await this.client.delete(`/leads/${id}`);
  }

  async convertLead(id: number): Promise<void> {
    await this.client.post(`/leads/${id}/convert`);
  }

  // ==================== CONTACTS ====================

  async getPersons(params?: {
    page?: number;
    search?: string;
  }): Promise<PaginatedResponse<Person>> {
    const response = await this.client.get<PaginatedResponse<Person>>('/contacts/persons', { params });
    return response.data;
  }

  async createPerson(data: Partial<Person>): Promise<Person> {
    const response = await this.client.post<{ data: Person }>('/contacts/persons', data);
    return response.data.data;
  }

  // ==================== ACTIVITIES ====================

  async getActivities(leadId: number): Promise<any[]> {
    const response = await this.client.get('/activities', {
      params: { lead_id: leadId }
    });
    return response.data.data;
  }

  async createActivity(data: {
    type: 'call' | 'meeting' | 'lunch' | 'email' | 'note';
    title: string;
    lead_id: number;
    description?: string;
    scheduled_from?: string;
    scheduled_to?: string;
  }): Promise<any> {
    const response = await this.client.post('/activities', data);
    return response.data.data;
  }
}

// Singleton
export const krayinAPI = new KrayinAPI({
  baseURL: 'https://crm.memudecore.com.br/api/v1',
});

export default krayinAPI;

Hook React para Leads

hooks/useLeads.ts

import { useState, useEffect, useCallback } from 'react';
import krayinAPI from '../api/krayin';

interface UseLeadsOptions {
  page?: number;
  perPage?: number;
  search?: string;
}

export function useLeads(options: UseLeadsOptions = {}) {
  const [leads, setLeads] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  const [pagination, setPagination] = useState({
    currentPage: 1,
    lastPage: 1,
    total: 0,
  });

  const fetchLeads = useCallback(async () => {
    try {
      setLoading(true);
      const response = await krayinAPI.getLeads({
        page: options.page || 1,
        per_page: options.perPage || 15,
        search: options.search,
      });

      setLeads(response.data);
      setPagination({
        currentPage: response.meta.current_page,
        lastPage: response.meta.last_page,
        total: response.meta.total,
      });
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  }, [options.page, options.perPage, options.search]);

  useEffect(() => {
    fetchLeads();
  }, [fetchLeads]);

  const createLead = async (data: any) => {
    const lead = await krayinAPI.createLead(data);
    setLeads(prev => [lead, ...prev]);
    return lead;
  };

  const updateLead = async (id: number, data: any) => {
    const lead = await krayinAPI.updateLead(id, data);
    setLeads(prev => prev.map(l => l.id === id ? lead : l));
    return lead;
  };

  const deleteLead = async (id: number) => {
    await krayinAPI.deleteLead(id);
    setLeads(prev => prev.filter(l => l.id !== id));
  };

  return {
    leads,
    loading,
    error,
    pagination,
    refetch: fetchLeads,
    createLead,
    updateLead,
    deleteLead,
  };
}

Componente de Lista de Leads

components/LeadsList.tsx

import React, { useState } from 'react';
import { useLeads } from '../hooks/useLeads';

export function LeadsList() {
  const [page, setPage] = useState(1);
  const [search, setSearch] = useState('');

  const { leads, loading, pagination, refetch } = useLeads({
    page,
    perPage: 10,
    search,
  });

  if (loading) return <div>Carregando...</div>;

  return (
    <div className="leads-list">
      <input
        type="text"
        placeholder="Buscar leads..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <table>
        <thead>
          <tr>
            <th>Título</th>
            <th>Valor</th>
            <th>Vendedor</th>
            <th>Status</th>
            <th>Ações</th>
          </tr>
        </thead>
        <tbody>
          {leads.map(lead => (
            <tr key={lead.id}>
              <td>{lead.title}</td>
              <td>R$ {lead.value?.toLocaleString('pt-BR')}</td>
              <td>{lead.sales_owner?.name}</td>
              <td>{lead.status?.name}</td>
              <td>
                <button onClick={() => window.location.href = `/leads/${lead.id}`}>
                  Ver
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      <div className="pagination">
        <button
          disabled={page === 1}
          onClick={() => setPage(p => p - 1)}
        >
          Anterior
        </button>
        <span>Página {pagination.currentPage} de {pagination.lastPage}</span>
        <button
          disabled={page === pagination.lastPage}
          onClick={() => setPage(p => p + 1)}
        >
          Próxima
        </button>
      </div>
    </div>
  );
}

4. Outras Integrações

Google Calendar

O Krayin tem integração nativa com Google Calendar:

# .env
GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=xxxxx
GOOGLE_REDIRECT_URI=https://crm.memudecore.com.br/google/oauth
GOOGLE_WEBHOOK_URI=https://crm.memudecore.com.br/google/webhook

IMAP Email

Configuração em: Settings → Email Configuration

WebForms

Criar formulários de captura em: Settings → WebForms


📚 Recursos Adicionais

  • N8N Docs: https://docs.n8n.io
  • Evolution API: https://doc.evolution-api.com
  • Krayin API Docs: https://crm.memudecore.com.br/api/admin/documentation

Última atualização: Janeiro 2026