Synkro pour les développeurs

Relie Synkro à tes outils : une API REST pour lire et écrire, des webhooks entrants pour publier un message depuis un script ou un outil d'alerte, des webhooks sortants pour être prévenu quand quelque chose arrive. Les intégrations sont une fonctionnalité de l'offre Essentiel. Les noms de champs, les en-têtes et les codes de cette page sont ceux de la version 1 de l'API.

Clés d'API

Un propriétaire ou un administrateur de l'organisation crée une clé dans Administration › Intégrations. La clé commence par syk_key_ et n'est affichée qu'une seule fois, à la création : copie-la tout de suite, Synkro n'en garde que l'empreinte.

Une clé reste côté serveur : jamais dans un navigateur, une application mobile ou un dépôt de code. Synkro refuse toute requête de l'API qui porte un en-tête Origin (réponse 403).

API REST

Base : https://integrations.mysynkro.fr/v1. Le document OpenAPI 3.1 complet, avec les formes de chaque ressource, est ici, au format JSON.

Authentification et format

Routes

Route Portée Rôle
GET /v1/me read l'intégration, son organisation, ses portées et ses canaux
GET /v1/channels read les canaux ouverts à la clé
GET /v1/channels/{channel_id} read un canal
GET /v1/channels/{channel_id}/members read les personnes visibles dans le canal
GET /v1/members read les membres internes de l'organisation
GET /v1/channels/{channel_id}/messages read les messages, du plus récent au plus ancien
POST /v1/channels/{channel_id}/messages write publie un message (text, 4 000 caractères au plus)
GET /v1/channels/{channel_id}/tickets read les tickets du projet du canal
POST /v1/channels/{channel_id}/tickets write crée un ticket
GET /v1/tickets/{key} read le détail d'un ticket (par exemple ACME-42)
PATCH /v1/tickets/{key} write change le statut, l'assigné, la priorité, le titre, la description ou le sprint
GET /v1/tickets/{key}/comments read les commentaires, du plus ancien au plus récent
POST /v1/tickets/{key}/comments write ajoute un commentaire

Les routes des abonnements (/v1/webhooks) sont décrites plus bas. Les pièces jointes sont listées (nom, type, taille) mais ne se téléchargent pas par l'API.

L'adresse e-mail des membres internes est le seul renvoi de ce type : la route GET /v1/members donne l'adresse e-mail des membres internes de l'organisation, jamais celle d'un externe. Les propriétaires et les administrateurs les voient déjà dans Personnes, et tes automatisations en ont besoin pour retrouver une personne de Synkro à partir d'un compte d'un autre outil. Les webhooks sortants, eux, ne portent jamais d'adresse e-mail.

Pagination

Une liste répond { "data": [...], "next_cursor": "…" }. Passe next_cursor dans le paramètre cursor pour la page suivante ; next_cursor vaut null à la fin. Le curseur est opaque, ne le fabrique pas. limit vaut 20 par défaut, 100 au plus (50 pour les messages).

Idempotence

Ajoute un en-tête Idempotency-Key (1 à 128 caractères) à un POST ou un PATCH pour pouvoir le rejouer sans doublon pendant 24 h. Même clé, même opération : tu reçois la première réponse, avec Idempotent-Replayed: true. Même clé pendant que la première requête tourne encore : 409. Même clé pour une autre opération : 422.

Erreurs

Une erreur répond { "error": { "code": "not_found", "message": "…", "request_id": "req_…" } }, avec un message en français.

HTTP Code Cause
400 validation JSON invalide, champ manquant ou en trop, curseur invalide
401 unauthenticated clé absente, inconnue, révoquée ou expirée
403 forbidden intégration désactivée, portée absente, requête avec un en-tête Origin
403 plan_required l'offre de l'organisation n'inclut pas les intégrations
403 org_read_only l'organisation est en lecture seule
403 org_suspended l'organisation est suspendue (la clé reste valable)
404 not_found ressource inconnue ou hors des canaux de la clé
405 method_not_allowed méthode non prévue sur cette route (en-tête Allow)
409 conflict opération déjà en cours avec cette clé d'idempotence
413 payload_too_large corps de plus de 1 Mio
422 limit_reached limite atteinte (par exemple 10 abonnements)
429 rate_limited trop de requêtes
500 internal erreur interne : donne la référence req_… au support

Limites

Chaque intégration peut envoyer 120 requêtes par minute, dont 30 écritures au plus. Tu peux en envoyer 120 d'un coup ; ensuite les jetons reviennent au fil de la minute. Chaque réponse porte RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset (secondes avant le prochain jeton). Au-delà, tu reçois 429 avec Retry-After (en secondes) : attends ce délai avant de réessayer.

Webhooks entrants

Un webhook entrant publie un message dans un canal depuis n'importe quel émetteur : un script, un outil d'alerte, une étape de CI. Un propriétaire ou un administrateur le crée dans Administration › Intégrations, en choisissant un nom, un avatar et un seul canal (jamais un canal confidentiel).

Synkro te donne une adresse secrète, affichée une seule fois : https://integrations.mysynkro.fr/hooks/in/syk_whk_…. Elle suffit pour publier : garde-la comme un mot de passe. Si elle fuite, renouvelle-la ; l'ancienne adresse répond 404 tout de suite.

Deux formats

Le format Synkro s'envoie à l'adresse telle quelle :

curl -X POST "https://integrations.mysynkro.fr/hooks/in/syk_whk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Le déploiement de **api-client** a échoué.",
    "title": "Déploiement",
    "title_url": "https://ci.exemple.fr/42",
    "fields": [{ "name": "Branche", "value": "main" }],
    "footer": "CI"
  }'

Au moins text ou title. Le texte est du Markdown de Synkro ; les champs inconnus sont ignorés ; 10 champs fields au plus. Ajoute ?wait=true pour recevoir { "message_id": "…", "url": "…" }. Les mentions s'écrivent <@id> avec l'identifiant d'une personne visible dans le canal ; @canal n'est jamais interprété.

Le format Slack s'envoie à la même adresse suivie de /slack. Synkro convertit les champs text, attachments et blocks en Markdown (gras, italique, liens, code) et ignore le reste (username, icon_url, channel…). Le format Slack ne crée jamais de mention, et @channel et @here restent du texte, sans notification. Beaucoup d'outils d'alerte savent déjà l'émettre : il suffit de coller l'adresse.

Réponses et limites

Signature facultative

Un administrateur peut exiger une signature : Synkro crée alors un secret whsec_…, affiché une seule fois. L'émetteur envoie X-Synkro-Timestamp (secondes Unix) et X-Synkro-Signature: v1=<hex>, où l'empreinte est le HMAC-SHA256 en hexadécimal de <horodatage>.<corps brut> avec le secret. Un horodatage à plus de 5 minutes de l'heure du serveur est refusé, et une requête sans signature aussi (401).

BODY='{"text":"Déploiement terminé"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SYNKRO_WEBHOOK_SECRET" | sed 's/^.* //')
curl -X POST "$SYNKRO_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -H "X-Synkro-Timestamp: $TS" \
  -H "X-Synkro-Signature: v1=$SIG" \
  -d "$BODY"

Dans une étape de GitHub Actions, ce script va dans un run:, avec l'adresse et le secret lus depuis les secrets du dépôt. Dans n8n, calcule l'empreinte dans un nœud « Crypto » (HMAC, SHA-256, sortie hexadécimale) puis envoie les deux en-têtes depuis un nœud « HTTP Request ». Dans Make, utilise la fonction sha256(texte; hex; secret) dans un module « HTTP › Make a request ». Dans les trois cas, signe exactement les octets que tu envoies.

Webhooks sortants

Un webhook sortant envoie une requête POST à l'adresse de ton choix quand un événement arrive. Un propriétaire ou un administrateur le crée dans Administration › Intégrations : adresse en https, événements souhaités, canaux. Le secret de signature whsec_… n'est affiché qu'à la création et à la rotation.

Événements

Les éditions et suppressions de messages, les réactions et les épingles ne sont pas émises. Un message écrit par une intégration n'est jamais renvoyé à cette même intégration, et une destination ne reçoit les messages des intégrations que si tu l'as demandé.

Charge

POST https://exemple.fr/synkro
Content-Type: application/json
User-Agent: Synkro-Webhooks/1.0 (+https://mysynkro.fr/developpeurs)
webhook-id: evt_…
webhook-timestamp: 1791555780
webhook-signature: v1,…
Synkro-Event: ticket.created

{
  "id": "evt_…",
  "type": "ticket.created",
  "version": 1,
  "created_at": "2026-10-09T14:03:00.000Z",
  "organization": { "id": "…", "slug": "nova", "name": "Nova Studio" },
  "data": {
    "ticket": { "key": "ACME-42", "title": "…" },
    "channel": { "id": "…", "name": "client-acme" },
    "actor": { "type": "person", "id": "…", "name": "Léa Martin", "external": false }
  }
}

data est un instantané pris à l'émission, avec les mêmes formes que l'API. La charge fait 64 Kio au plus : au-delà, le texte d'un message ou d'un commentaire est tronqué et marqué truncated: true.

Vérifier la signature

Synkro suit la spécification Standard Webhooks. La signature est base64(HMAC-SHA256(clé, "<webhook-id>.<webhook-timestamp>.<corps>")), dans l'en-tête webhook-signature: v1,<signature>. La clé est le secret whsec_… sans son préfixe, décodé du base64. Vérifie sur le corps brut, avant tout décodage JSON, refuse un webhook-timestamp à plus de 5 minutes de ton heure, et compare en temps constant. Pendant la rotation d'un secret, qui dure 24 h, l'en-tête porte deux signatures séparées par une espace : accepte la requête si l'une des deux correspond.

Node

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

export function verify(secret, headers, rawBody) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest();
  return headers["webhook-signature"].split(" ").some((part) => {
    const given = Buffer.from(part.replace(/^v1,/, ""), "base64");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

Python

import base64, hashlib, hmac, time

def verify(secret, headers, raw_body):
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + raw_body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(part.removeprefix("v1,"), expected)
        for part in headers["webhook-signature"].split(" ")
    )

PHP

function verify(string $secret, array $headers, string $rawBody): bool {
    $id = $headers["webhook-id"];
    $timestamp = $headers["webhook-timestamp"];
    if (abs(time() - (int) $timestamp) > 300) return false;
    $key = base64_decode(preg_replace('/^whsec_/', '', $secret));
    $expected = base64_encode(hash_hmac("sha256", "$id.$timestamp.$rawBody", $key, true));
    foreach (explode(" ", $headers["webhook-signature"]) as $part) {
        if (hash_equals($expected, preg_replace('/^v1,/', '', $part))) return true;
    }
    return false;
}

Réponse, relances et doublons

Abonnements par l'API

Un outil peut créer lui-même ses webhooks sortants avec sa clé (« REST Hooks »), par exemple pour un déclencheur « quand un ticket est créé ».

curl -X POST "https://integrations.mysynkro.fr/v1/webhooks" \
  -H "Authorization: Bearer syk_key_…" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://exemple.fr/synkro", "events": ["ticket.created"] }'

La réponse (201) contient id, url, events, channel_ids et le secret whsec_…, rendu cette seule fois. Si tu rejoues la création avec la même Idempotency-Key, tu reçois le même abonnement sans secret : garde celui de la première réponse. channel_ids est facultatif : sans lui, l'abonnement couvre tous les canaux ouverts à la clé. Les autres routes :

Un abonnement est un webhook sortant comme les autres : mêmes événements, même signature, mêmes relances. Il apparaît dans Administration › Intégrations sous l'intégration, et un administrateur peut le supprimer. Chaque intégration a 10 abonnements au plus. Créer, supprimer et tester demandent la portée write.

Connecteurs

Sans écrire de code, relie Synkro à tes automatisations avec une clé d'API :

Les deux s'appuient sur l'API REST et les abonnements décrits plus haut. Tu peux aussi utiliser le module HTTP de n'importe quel outil d'automatisation.

Aperçus des liens et SynkroBot

Quand un message d'un canal contient un lien externe, Synkro peut en afficher un aperçu (titre, description, image). Pour cela, un agent nommé SynkroBot ouvre la page, avec l'en-tête User-Agent: SynkroBot/1.0, lit ses balises de description et copie l'image sur le site de Synkro. Les personnes qui lisent le message ne contactent jamais le site du lien.

Recettes

Des recettes pas à pas, jouées sur de vraies charges, pour relier Synkro à des outils courants :