Skip to content

Salesforce Integration

Salesforce dient als führendes CRM für Kundenbeziehungen.


Sync-Strategie

┌─────────────────┐          ┌─────────────────┐
│   Salesforce    │◄────────►│  MORELO Suite   │
│   (Master CRM)  │  2-Way   │    (D1 Cache)   │
└─────────────────┘   Sync   └─────────────────┘
Richtung Daten Trigger
SF → Suite Kontakte, Opportunities Webhook / Polling
Suite → SF Aktivitäten, Notizen API Push

Salesforce Objects

Contact (Kunden)

interface SalesforceContact {
  Id: string;
  FirstName: string;
  LastName: string;
  Email: string;
  Phone: string;
  MailingAddress: {
    street: string;
    city: string;
    postalCode: string;
    country: string;
  };
  // Custom Fields
  MORELO_Customer_ID__c: string;
  Preferred_Model__c: string;
  Last_Visit__c: string;
}

Opportunity (Verkaufschance)

interface SalesforceOpportunity {
  Id: string;
  Name: string;
  AccountId: string;
  ContactId: string;
  StageName: string; // Prospecting, Qualification, Proposal, Closed Won
  Amount: number;
  CloseDate: string;
  // Custom Fields
  Vehicle_Model__c: string;
  Configuration_URL__c: string;
}

API Endpoints

REST API

# Kontakte abrufen
GET /services/data/v59.0/query
  ?q=SELECT+Id,FirstName,LastName,Email+FROM+Contact+WHERE+LastModifiedDate+>+2026-01-01T00:00:00Z

# Kontakt erstellen
POST /services/data/v59.0/sobjects/Contact
{
  "FirstName": "Hans",
  "LastName": "Mustermann",
  "Email": "hans@email.de",
  "MORELO_Customer_ID__c": "cust_abc123"
}

# Opportunity aktualisieren
PATCH /services/data/v59.0/sobjects/Opportunity/{id}
{
  "StageName": "Proposal",
  "Configuration_URL__c": "https://konfigurator.morelo.de/c/xyz"
}

Bulk API (für große Datenmengen)

# Bulk Job erstellen
POST /services/data/v59.0/jobs/ingest
{
  "operation": "upsert",
  "object": "Contact",
  "externalIdFieldName": "MORELO_Customer_ID__c"
}

# CSV Daten hochladen
PUT /services/data/v59.0/jobs/ingest/{jobId}/batches
Content-Type: text/csv

MORELO_Customer_ID__c,FirstName,LastName,Email
cust_001,Hans,Mustermann,hans@email.de
cust_002,Maria,Schmidt,maria@email.de

SonicJS Integration

Route: /api/integrations/salesforce

// src/routes/integrations/salesforce.ts
import { Hono } from 'hono';

const app = new Hono();

// Salesforce OAuth Token
async function getAccessToken(env: Env) {
  const cached = await env.KV.get('sf:token');
  if (cached) return cached;

  const response = await fetch(
    `${env.SF_INSTANCE_URL}/services/oauth2/token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: env.SF_CLIENT_ID,
        client_secret: env.SF_CLIENT_SECRET
      })
    }
  );

  const { access_token, expires_in } = await response.json();
  await env.KV.put('sf:token', access_token, {
    expirationTtl: expires_in - 300
  });

  return access_token;
}

// Kontakte synchronisieren
app.post('/sync/contacts', async (c) => {
  const token = await getAccessToken(c.env);

  // Geänderte Kontakte seit letztem Sync
  const lastSync = await c.env.KV.get('sf:lastSync') || '2020-01-01T00:00:00Z';

  const query = `SELECT Id,FirstName,LastName,Email,Phone FROM Contact WHERE LastModifiedDate > ${lastSync}`;

  const response = await fetch(
    `${c.env.SF_INSTANCE_URL}/services/data/v59.0/query?q=${encodeURIComponent(query)}`,
    { headers: { Authorization: `Bearer ${token}` } }
  );

  const { records } = await response.json();

  // In D1 speichern
  for (const contact of records) {
    await c.env.DB.prepare(`
      INSERT OR REPLACE INTO customers (id, email, first_name, last_name, salesforce_id)
      VALUES (?, ?, ?, ?, ?)
    `).bind(
      crypto.randomUUID(),
      contact.Email,
      contact.FirstName,
      contact.LastName,
      contact.Id
    ).run();
  }

  await c.env.KV.put('sf:lastSync', new Date().toISOString());

  return c.json({ synced: records.length });
});

export default app;

Webhooks (Outbound Messages)

Salesforce kann bei Änderungen Webhooks senden:

// Webhook Endpoint
app.post('/webhooks/salesforce', async (c) => {
  const xml = await c.req.text();

  // SOAP XML parsen
  const contactId = extractFromXml(xml, 'sf:Id');
  const email = extractFromXml(xml, 'sf:Email');

  // Lokalen Cache invalidieren
  await c.env.KV.delete(`customer:sf:${contactId}`);

  // ACK response
  return c.text(`<?xml version="1.0"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Body>
        <notificationsResponse>
          <Ack>true</Ack>
        </notificationsResponse>
      </soapenv:Body>
    </soapenv:Envelope>
  `, 200, { 'Content-Type': 'text/xml' });
});

Environment Variables

# Salesforce Connected App
SF_INSTANCE_URL=https://yourorg.my.salesforce.com
SF_CLIENT_ID=your_client_id
SF_CLIENT_SECRET=your_client_secret

Referenzen