Vortex

API ET WEBHOOKS

API et webhooks de Vortex

Lisez vos prospects, clients et analytics depuis vos propres outils, et recevez un appel signé quand il se passe quelque chose dans votre espace.

Inclus dans le plan Agency. Lecture seule : l'API ne peut rien créer, modifier ni supprimer.

Authentification

Créez une clé dans Réglages, API et webhooks. Elle ressemble à vtx_ suivi de 8 caractères, d'un tiret bas et de 32 caractères. Elle n'est affichée qu'une fois : Vortex n'en garde qu'une empreinte, conservez-la dans votre gestionnaire de mots de passe. Envoyez-la dans l'en-tête Authorization.

Exemple
curl https://usevortex.tech/api/v1/prospects \
  -H "Authorization: Bearer vtx_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  • Une clé appartient à un seul espace de travail et ne peut lire que cet espace.
  • Une clé révoquée cesse de fonctionner immédiatement. La révocation marche sur tous les plans.
  • Les clés ont des portées. L'API accepte les clés de portée api:read.
  • Si l'espace quitte le plan Agency ou que son abonnement s'arrête, chaque requête est refusée avec le code 403.

Limites

  • 60 requêtes par minute et par clé, par fenêtres fixes d'une minute. Au-delà, vous recevez 429 avec un en-tête Retry-After en secondes. Chaque réponse réussie porte X-RateLimit-Limit.
  • Les listes renvoient 25 éléments par défaut et au plus 100 (paramètre limit).
  • Jusqu'à 10 clés actives et 5 adresses de webhook par espace de travail.

Réponses et erreurs

Succès : { "data": ... }. Les listes ajoutent "next_cursor" (null sur la dernière page). Erreurs : { "error": { "code": "...", "message": "..." } }.

StatutCodeSignification
400invalid_limit, invalid_cursor, invalid_status, invalid_daysUn paramètre n'est pas valide
401invalid_keyClé absente, mal formée, inconnue ou révoquée (toujours la même réponse)
403plan_required, subscription_inactive, insufficient_scope, feature_unavailableLa clé est valide mais l'espace ne peut pas l'utiliser
404not_foundAucun objet de ce type dans votre espace
429rate_limitedTrop de requêtes

Points d'accès

Tous les points d'accès utilisent GET et commencent par https://usevortex.tech/api/v1.

Point d'accèsParamètresRenvoie
GET /api/v1-Vérifie la clé et décrit l'API
GET /api/v1/prospectsstatus, limit, cursorLes prospects, du plus récent au plus ancien
GET /api/v1/prospects/{id}-Un prospect
GET /api/v1/clientslimit, cursorLes clients : id, prospect_id, website_url
GET /api/v1/clients/{id}/analyticsdays (1-90)Les chiffres agrégés du portail, déjà collectés
GET /api/v1/paymentsstatus, limit, cursorLes paiements de vos clients encaissés via Stripe Connect

Valeurs de statut d'un prospect : new, active, standby, client, blacklisted. Valeurs de statut d'un paiement : paid, pending, expired, void, uncollectible, open. Repassez next_cursor dans cursor pour obtenir la page suivante.

Un prospect
{
  "data": [
    { "id": "…", "status": "new", "city": "Lyon", "niche": "Bakery", "siren": "123456789", "created_at": "2026-10-01T10:00:00.000Z" }
  ],
  "next_cursor": "eyJhdCI6…"
}
Analytics d'un client
{ "data": { "client_id": "…", "period": { "days": 30, "from": "…", "to": "…" },
  "visitors": 120, "pageviews": 340, "form_submits": 6, "call_clicks": 11,
  "conversion_rate_pct": 5, "avg_load_time_ms": 900, "truncated": false } }

Les paiements sont ceux que vos clients vous ont versés via Vortex (amount_cents, application_fee_cents, currency, status, created_at). Les prospects d'exemple montrés aux nouveaux comptes ne sont pas renvoyés.

Champs jamais renvoyés

Le contenu issu de Google Places n'est exposé ni par l'API ni par les webhooks : name, address, phone, hours, rating, reviews, google_maps_url. Le nom d'entreprise d'un client n'est pas exposé non plus. Les adresses e-mail, les liens de portail et les liens de facture ne sont jamais renvoyés.

Webhooks

Ajoutez une adresse dans Réglages, API et webhooks, et choisissez ses événements. Vortex envoie alors un POST avec un corps JSON quand l'événement se produit. Le secret de signature (whsec_…) n'est affiché qu'une fois, à l'ajout de l'adresse.

  • https:// uniquement, le port par défaut et un vrai nom de domaine (ni adresse IP, ni localhost, ni nom interne).
  • Le nom doit pointer vers des adresses publiques. C'est revérifié à chaque envoi, et la connexion va à l'adresse vérifiée.
  • Les redirections ne sont pas suivies.

Événements et contenu

ÉvénementQuand
prospect.createdUn prospect est ajouté, à la main ou depuis une recherche
prospect.status_changedUn prospect passe à une autre étape du pipeline (previous_status est inclus)
payment.receivedUn paiement client est réglé via Stripe Connect
webhook.testSeulement quand vous cliquez sur « Envoyer un test »
Contenu
{
  "id": "6f1c1d5e-6b0e-4d0b-9a3a-0e5a1b2c3d4e",
  "type": "prospect.created",
  "created": "2026-10-23T10:00:00.000Z",
  "data": { "id": "…", "status": "new", "city": "Lyon", "niche": "Bakery", "siren": null, "created_at": "…" }
}

id est l'identifiant de l'événement : un même événement n'est jamais livré deux fois à une même adresse, et l'identifiant reste le même lors des reprises, vous pouvez donc l'utiliser pour dédoublonner.

En-têteValeur
Vortex-Event-IdL'identifiant de l'événement
Vortex-Event-TypeLe type d'événement
Vortex-Delivery-Attempt1 pour le premier essai
Vortex-Signaturet=<unix seconds>,v1=<hex>

Vérifier la signature

v1 vaut HMAC-SHA256(secret, "<t>.<corps brut de la requête>") en hexadécimal. Calculez-le sur le corps brut, comparez en temps constant et rejetez les horodatages de plus de 5 minutes.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "");
  const wanted = Buffer.from(expected);
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}
Python
import hashlib, hmac, time

def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get("v1", ""), expected)

Envoi, reprises et échecs

  • Répondez par un code 2xx dans les 10 secondes. Tout le reste, redirection comprise, est un échec.
  • Un envoi en échec est repris après 1 min, 5 min, 30 min, 2 h, 6 h (6 essais au total). Le calendrier est stocké en base, il survit aux redémarrages.
  • Après 10 échecs de suite sur une même adresse, elle est désactivée et la raison s'affiche dans les Réglages. La réactiver remet le compteur à zéro.
  • Le journal des envois, dans les Réglages, garde les 30 derniers jours : date, événement, résultat, essais et code HTTP ou code d'erreur court. La réponse de votre serveur n'est jamais enregistrée.
API et webhooks de Vortex — Vortex