Klantly Developers

Du formulaire web au prospect

Enregistrez chaque demande envoyée via le formulaire de votre site comme prospect dans Klantly, avec le message en note et, si vous le souhaitez, un deal.

Un visiteur remplit le formulaire de contact de votre site. En quelques requêtes, cette demande devient un prospect dans Klantly, avec le message joint et une carte sur votre tableau de pipeline.

Attention

Envoyez le formulaire à votre propre serveur et faites les requêtes API depuis celui-ci. Une clé API n'a jamais sa place dans le code de votre site : n'importe qui peut l'y lire.

Ce dont vous avez besoin

Une clé API avec ces scopes :

Scope Pour quoi faire
customers.read Vérifier si l'adresse e-mail est déjà connue.
customers.write Créer le prospect et ajouter la note.
deals.write Facultatif : placer le prospect sur le tableau de pipeline.

Étape 1 : le client existe-t-il déjà ?

Une adresse e-mail est unique au sein de votre entreprise. Vérifiez donc d'abord si elle est déjà connue :

cURL
curl --globoff "https://app.klantly.com/api/v1/customers?filter[email]=jan@example.com" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"

Si data est vide, créez le prospect. Sinon, utilisez l'id du client renvoyé.

Étape 2 : créer le prospect

cURL
curl -X POST "https://app.klantly.com/api/v1/customers" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: form-8f14e45f" \
  -d '{"email": "jan@example.com", "name": "Jan de Vries", "phone": "+31 6 12345678"}'

Donnez à chaque envoi sa propre Idempotency-Key, par exemple l'id de l'envoi dans votre propre base de données. Si un visiteur clique deux fois sur envoyer, ou si votre serveur réessaie après un délai dépassé, un seul prospect est créé. Pour en savoir plus, voir Idempotence.

Si deux envois avec la même adresse e-mail arrivent presque en même temps, le second peut renvoyer une 422 avec une erreur sur email. Recherchez alors à nouveau le client, comme à l'étape 1.

Étape 3 : le message en note

cURL
curl -X POST "https://app.klantly.com/api/v1/customers/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/notes" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: form-8f14e45f-note" \
  -d '{"content": "Via le formulaire du site : je souhaite un devis pour une véranda de 5 sur 3 mètres."}'

Étape 4 : un deal sur le tableau (facultatif)

cURL
curl -X POST "https://app.klantly.com/api/v1/deals" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: form-8f14e45f-deal" \
  -d '{"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70", "title": "Véranda 5x3 m"}'

Un client n'a qu'un seul deal ouvert à la fois. S'il en a déjà un, vous recevez 409 avec le code conflict et l'id de ce deal dans deal_id. Ce n'est pas une vraie erreur : la demande se rattache alors au deal déjà en cours.

Tout ensemble

PHP
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

function sendLeadToKlantly(array $form, string $submissionId): void
{
    $klantly = new Client([
        'base_uri' => 'https://app.klantly.com/api/v1/',
        'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
    ]);

    // 1. Le client existe-t-il déjà ?
    $found = json_decode((string) $klantly->get('customers', [
        'query' => ['filter' => ['email' => $form['email']]],
    ])->getBody(), true)['data'];

    // 2. Sinon : créer le prospect.
    $customer = $found[0] ?? json_decode((string) $klantly->post('customers', [
        'headers' => ['Idempotency-Key' => "form-{$submissionId}"],
        'json' => array_filter(['email' => $form['email'], 'name' => $form['name'], 'phone' => $form['phone']]),
    ])->getBody(), true)['data'];

    // 3. Le message en note.
    $klantly->post("customers/{$customer['id']}/notes", [
        'headers' => ['Idempotency-Key' => "form-{$submissionId}-note"],
        'json' => ['content' => $form['message']],
    ]);

    // 4. Un deal ; 409 signifie qu'un deal ouvert existe déjà.
    try {
        $klantly->post('deals', [
            'headers' => ['Idempotency-Key' => "form-{$submissionId}-deal"],
            'json' => ['customer_id' => $customer['id'], 'title' => $form['subject']],
        ]);
    } catch (ClientException $e) {
        if ($e->getResponse()->getStatusCode() !== 409) {
            throw $e;
        }
    }
}
Node.js
const BASE_URL = 'https://app.klantly.com/api/v1';

async function klantly(method, path, { body, idempotencyKey } = {}) {
  const response = await fetch(`${BASE_URL}/${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
      ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  return { status: response.status, json: await response.json() };
}

export async function sendLeadToKlantly(form, submissionId) {
  // 1. Le client existe-t-il déjà ?
  const found = await klantly('GET', `customers?${new URLSearchParams({ 'filter[email]': form.email })}`);
  if (found.status !== 200) throw new Error(found.json.detail ?? found.json.title);
  let customer = found.json.data[0];

  // 2. Sinon : créer le prospect.
  if (!customer) {
    const created = await klantly('POST', 'customers', {
      body: { email: form.email, name: form.name, ...(form.phone ? { phone: form.phone } : {}) },
      idempotencyKey: `form-${submissionId}`,
    });
    if (created.status !== 201) throw new Error(created.json.detail ?? created.json.title);
    customer = created.json.data;
  }

  // 3. Le message en note.
  await klantly('POST', `customers/${customer.id}/notes`, {
    body: { content: form.message },
    idempotencyKey: `form-${submissionId}-note`,
  });

  // 4. Un deal ; 409 signifie qu'un deal ouvert existe déjà.
  const deal = await klantly('POST', 'deals', {
    body: { customer_id: customer.id, title: form.subject },
    idempotencyKey: `form-${submissionId}-deal`,
  });
  if (deal.status !== 201 && deal.status !== 409) throw new Error(deal.json.detail ?? deal.json.title);
}

Gérer les erreurs

  • 422 avec validation_failed : par exemple une adresse e-mail invalide. errors indique pour chaque champ ce qui ne va pas. Laissez le visiteur corriger.
  • 429 avec rate_limited : attendez le nombre de secondes indiqué dans Retry-After et réessayez. Voir Limites de débit.
  • Une erreur 5xx ou un délai dépassé : réessayez plus tard avec la même Idempotency-Key. Placez de préférence l'envoi dans une file d'attente : ainsi, aucune demande ne se perd.

Astuce

Vous voulez savoir quand un prospect devient client ? Écoutez l'événement customer.converted avec un webhook.

Dernière mise à jour le 15 septembre 2026