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
- API REST
- Webhooks entrants
- Webhooks sortants
- Abonnements par l'API
- Connecteurs
- Aperçus des liens et SynkroBot
- Recettes
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.
-
Portées :
read(lecture seule) oureadetwrite(lecture et écriture). Une clé n'a jamais accès aux messages privés. - Canaux : tu choisis les canaux ouverts à la clé. Les canaux confidentiels ne peuvent pas lui être ouverts.
- Expiration : sans expiration (par défaut), 1 an ou 90 jours. Une organisation a 20 clés actives au plus.
- Révocation : un administrateur révoque une clé à tout moment, et elle cesse de fonctionner tout de suite.
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
-
Envoie ta clé dans l'en-tête
Authorization: Bearer syk_key_…, jamais dans l'adresse. -
JSON en UTF-8, noms de champs en
snake_case, dates au format ISO 8601 en UTC (2026-10-09T14:03:00.000Z), identifiants opaques. Corps de requête : 1 Mio au plus. -
Chaque réponse porte
Synkro-Request-Id: donne-le au support en cas de problème. - La version 1 ne reçoit que des ajouts (champ, route) : ignore les champs que tu ne connais pas.
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
-
Succès : 200 avec le corps
ok. Les erreurs répondent en texte :no_service(404, adresse inconnue ou désactivée),invalid_payloadetno_text(400),invalid_signature(401),channel_is_archivedetorg_read_only(403),payload_too_large(413). - Corps de 100 Kio au plus. Aucun en-tête CORS.
-
Par webhook : 10 messages d'un coup, puis 1 par seconde. Au-delà, 429 avec
Retry-After. Par organisation : 300 messages par minute. - Synkro note l'heure, le statut, la taille et le format des 50 derniers appels (7 jours au plus), jamais leur contenu.
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
message.posted: un message est publié dans un canal ;ticket.created: un ticket est créé ;-
ticket.updated: un champ d'un ticket change, hors statut et assignation ; ticket.status_changed: le statut change ;ticket.assigned: l'assigné change ;ticket.commented: un commentaire est ajouté ;-
member.joined: un interne rejoint l'organisation, ou un externe rejoint un canal (sans adresse e-mail) ; -
call.startedetcall.ended: un appel de canal démarre ou se termine (jamais un appel privé) ; file.uploaded: une pièce jointe est prête ;ping: l'envoi de test.
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
- Réponds par un code 2xx, en 10 s au plus. Tout autre résultat (délai dépassé, erreur réseau, 408, 429, 4xx, 5xx) est relancé.
-
Relances : 8 tentatives en tout, après 0, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h et 24 h,
soit environ 45 h. Un
Retry-Afteren 429 ou 503 est respecté. - Un code 410 désactive la destination tout de suite. Une destination sans aucun succès depuis 24 h, après au moins 10 échecs, est désactivée, et les administrateurs en sont prévenus par e-mail.
-
Livraison au moins une fois : un même événement peut arriver deux fois. Déduplique sur
le champ
id, identique d'une tentative et d'un rejeu à l'autre. -
Aucun ordre n'est garanti : une relance peut arriver après un événement plus récent.
Trie sur
created_at. -
L'historique des 30 derniers jours, le rejeu d'une livraison et l'envoi d'un
pingde test sont dans l'écran de la destination. Les adresses internes ou privées sont refusées.
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 :
GET /v1/webhooksetGET /v1/webhooks/{id}(read) ;DELETE /v1/webhooks/{id}(write, réponse 204) ;POST /v1/webhooks/{id}/test(write) : envoie un événement ping.
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 :
- n8n : un nœud Synkro, pour publier des messages, gérer des tickets et déclencher un scénario sur un événement ;
- Make : une application Synkro, avec les mêmes actions et les mêmes déclencheurs.
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.
- Un administrateur active ou coupe les aperçus dans les réglages de l'organisation ; coupés, plus aucune page n'est ouverte.
- SynkroBot n'ouvre que les liens des canaux, jamais dans un message privé.
- Les aperçus gardés en cache durent 7 jours. Si ton site ne veut pas de l'aperçu, il suffit de ne pas publier ses balises de description.
Recettes
Des recettes pas à pas, jouées sur de vraies charges, pour relier Synkro à des outils courants :
-
GitHub Actions : l'étape
slackapi/slack-github-actionvers le format Slack ; - Stripe : les événements, mis en forme par Make ;
- Tally : les réponses aux formulaires, mises en forme par Make.
Dernière mise à jour : 22 octobre 2026.