L'API Hou.la vous permet d'intégrer la création et la gestion de liens courts directement dans vos applications, scripts ou services.
https://hou.la/apiToutes les requêtes à l'API doivent être authentifiées en utilisant une clé API. Vous pouvez créer et gérer vos clés API depuis votre espace Manager.
Incluez votre clé API dans l'en-tête X-API-Key de chaque requête :
X-API-Key: houla_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxToutes les ressources (liens, tags, webhooks, domaines, etc.) sont rattachées à un workspace et non directement à un compte utilisateur. Chaque utilisateur dispose d'un workspace personnel créé automatiquement à l'inscription.
Pour cibler un workspace spécifique, ajoutez l'en-tête X-Workspace-Id à vos requêtes :
X-Workspace-Id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxSi cet en-tête est omis, le workspace personnel par défaut de l'utilisateur est utilisé automatiquement.
curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "X-Workspace-Id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/page"
}'POST/link
| Paramètre | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | L'URL longue à raccourcir |
title | string | Non | Un titre pour identifier le lien |
key | string | Non | Une clé personnalisée (ex: "mon-lien") |
utm_source | string | Non | Source de la campagne (ex: "google", "newsletter", "facebook") |
utm_medium | string | Non | Support marketing (ex: "cpc", "email", "social") |
utm_campaign | string | Non | Nom de la campagne (ex: "soldes-ete-2026") |
utm_term | string | Non | Mots-clés payés (pour les campagnes publicitaires) |
utm_content | string | Non | Contenu spécifique (ex: "bouton-header", "lien-footer") |
utm_id | string | Non | Identifiant de campagne pour le suivi avancé |
ephemeralDuration | string | Non | Durée de vie du lien éphémère. Valeurs : 1h, 6h, 12h, 24h, 48h |
customExpiresAt | string | Non | Date d'expiration personnalisée (ISO 8601, ex: 2026-06-15T23:59:59.000Z). Mutuellement exclusif avec ephemeralDuration. La clé n'est pas recyclée. |
password | string | Non | Mot de passe pour protéger l'accès au lien (1-100 caractères). Hashé avec bcrypt. Sécurité renforcée. |
fbPixelId | string | Non | Facebook Pixel ID (10-20 chiffres, ex: 123456789012345). Le pixel se déclenche lors du clic pour constituer une audience de retargeting. |
googleTagId | string | Non | Google Tag ID (format G-XXX, AW-XXX, DC-XXX ou UA-XXX). Supporte gtag.js. |
tiktokPixelId | string | Non | TikTok Pixel ID (format CXXX..., ex: CABCDEF1234567890). Le pixel se déclenche lors du clic pour le suivi TikTok Ads. |
ogTitle | string | Non | Titre Open Graph personnalisé affiché lors du partage sur les réseaux sociaux (max 200 caractères) |
ogDescription | string | Non | Description Open Graph personnalisée affichée lors du partage (max 500 caractères) |
ogImageUrl | string | Non | URL de l'image Open Graph (doit être une URL valide commençant par http:// ou https://) |
tagIds | string[] | Non | Liste d'identifiants de tags à associer au lien (UUID). Les tags doivent exister au préalable. |
customDomainId | string | Non | PAGES.API_DOC.CREATE_CUSTOM_DOMAIN_ID_DESC |
maxHits | number | Non | Nombre maximum de clics autorisés sur le lien (entre 1 et 1 000 000). Une fois atteint, le lien expire automatiquement. |
isCloaked | boolean | Non | Activer le Link Cloaking (<code class="code-inline">true</code>/<code class="code-inline">false</code>). Masque l'URL de destination dans la barre d'adresse du visiteur. |
deepLinkIos | string | Non | Schéma URI iOS pour ouvrir l'app native (ex: instagram://user?username=monprofil). Mobile uniquement. |
deepLinkAndroid | string | Non | Intent URL Android pour ouvrir l'app native (ex: intent://user?username=monprofil#Intent;scheme=instagram;package=com.instagram.android;end). Mobile uniquement. |
deepLinkFallbackUrl | string | Non | URL de repli si l'app n'est pas installée (ex: lien App Store, Play Store ou page web). Si non renseigné, la redirection standard s'applique. |
ephemeralDuration, le lien expirera automatiquement après la durée indiquée. La clé sera alors recyclée et pourra être réutilisée.customExpiresAt (ISO 8601), le lien expirera à cette date précise. La clé n'est pas recyclée. Mutuellement exclusif avec ephemeralDuration.password, le visiteur devra saisir le mot de passe avant d'être redirigé. Le mot de passe est hashé avec bcrypt (jamais stocké en clair). Sécurité renforcée.curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/ma-page-tres-longue",
"title": "Ma campagne marketing",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "promo-janvier-2026"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/offre-limitee",
"title": "Promo flash 24h",
"ephemeralDuration": "24h"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/evenement",
"title": "Inscription ouverte jusqu'au 15 juin",
"customExpiresAt": "2026-06-15T23:59:59.000Z"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/document-confidentiel",
"title": "Document privé",
"password": "secret123"
}'201 Created
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/ma-page-tres-longue",
"shortUrl": "https://hou.la/xY7k9",
"title": "Ma campagne marketing",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 0,
"flashsCount": 0,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}{
"id": "def456",
"key": "aB3cD",
"url": "https://example.com/offre-limitee",
"shortUrl": "https://hou.la/aB3cD",
"title": "Promo flash 24h",
"hasPassword": false,
"isEphemeral": true,
"ephemeralDuration": "24h",
"expiresAt": "2026-01-29T10:30:00Z",
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}{
"id": "jkl012",
"key": "mN4pQ",
"url": "https://example.com/evenement",
"shortUrl": "https://hou.la/mN4pQ",
"title": "Inscription ouverte jusqu'au 15 juin",
"hasPassword": false,
"isEphemeral": false,
"expiresAt": "2026-06-15T23:59:59.000Z",
"createdAt": "2026-02-12T10:30:00Z",
"status": "active"
}{
"id": "ghi789",
"key": "zK8mP",
"url": "https://example.com/document-confidentiel",
"shortUrl": "https://hou.la/zK8mP",
"title": "Document privé",
"hasPassword": true,
"isEphemeral": false,
"hitsCount": 0,
"flashsCount": 0,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}GET /api/link/{id}/qrcode (voir section 5).GET/link/{key}/availability
curl -X GET "https://hou.la/api/link/mon-lien/availability"200 OK
{
"available": true
}GET/link/{id}
Récupère les détails complets d'un lien par son UUID, incluant les statistiques des 90 derniers jours et les tags.
curl -X GET "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/ma-page-tres-longue",
"shortUrl": "https://hou.la/xY7k9",
"title": "Ma campagne marketing",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 42,
"flashsCount": 5,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}GET/link/{id}/qrcode
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
width | number | 300 | Largeur en pixels (50-1000) |
margin | number | 2 | Marge autour du QR code (0-10) |
darkColor | string | #000000 | Couleur des modules (hex) |
lightColor | string | #FFFFFF | Couleur de fond (hex) |
errorCorrectionLevel | string | M | Niveau de correction : L, M, Q, H |
format | string | png | Format de sortie : png ou svg |
curl -X GET "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890/qrcode?width=500&darkColor=%23FF5722" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"base64": "iVBORw0KGgoAAAANSUhEUgAA...",
"dataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}{
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 300 300\">...</svg>"
}GET/link
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
page | number | 1 | Numéro de la page |
limit | number | 20 | Nombre de résultats par page (max: 100) |
curl -X GET "https://hou.la/api/link?page=1&limit=10" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"data": [
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/page1",
"shortUrl": "https://hou.la/xY7k9",
"title": "Lien 1",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 42,
"createdAt": "2026-01-28T10:30:00Z"
},
...
],
"total": 55,
"page": 1,
"pageCount": 6,
"count": 10
}PATCH/link/{id}
| Paramètre | Type | Description |
|---|---|---|
url | string | La nouvelle URL de destination |
title | string | Le nouveau titre du lien |
password | string | Nouveau mot de passe (1-100 caractères), ou null pour retirer la protection |
isEphemeral | boolean | Activer/désactiver le mode éphémère |
ephemeralDuration | string | Durée éphémère : 1h, 6h, 12h, 24h, 48h |
customExpiresAt | string | Date d'expiration personnalisée (ISO 8601), ou null pour retirer l'expiration |
fbPixelId | string | Facebook Pixel ID (10-20 chiffres), ou null pour retirer le pixel |
googleTagId | string | Google Tag ID (G-XXX, AW-XXX, DC-XXX, UA-XXX), ou null pour retirer |
tiktokPixelId | string | TikTok Pixel ID (CXXX...), ou null pour retirer |
ogTitle | string | null | Nouveau titre Open Graph, ou null pour retirer |
ogDescription | string | null | Nouvelle description Open Graph, ou null pour retirer |
ogImageUrl | string | null | Nouvelle URL d'image Open Graph, ou null pour retirer |
tagIds | string[] | null | Liste d'identifiants de tags (UUID) à associer au lien, ou null pour retirer tous les tags |
maxHits | number | null | Nombre maximum de clics autorisés (1 à 1 000 000), ou null pour retirer la limite |
isCloaked | boolean | Activer ou désactiver le Link Cloaking (true/false) |
deepLinkIos | string | null | Schéma URI iOS, ou null pour supprimer |
deepLinkAndroid | string | null | Intent URL Android, ou null pour supprimer |
deepLinkFallbackUrl | string | null | URL de repli, ou <code class="code-inline">null</code> pour supprimer |
curl -X PATCH "https://hou.la/api/link/abc123" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Nouveau titre",
"url": "https://example.com/nouvelle-destination"
}'200 OK
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/nouvelle-destination",
"shortUrl": "https://hou.la/xY7k9",
"title": "Nouveau titre",
"hasPassword": false,
"updatedAt": "2026-01-28T14:00:00Z"
}DELETE/link/{id}
Supprime un lien (suppression logique). La clé peut être libérée pour réutilisation.
curl -X DELETE "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"success": true,
"message": "Lien supprimé avec succès"
}Uploadez une image personnalisée qui sera optimisée (1200×630, WebP) et stockée sur notre CDN. L'image remplace automatiquement le champ ogImageUrl du lien.
POST/manager/link/{id}/og-image
Le body doit être de type <code class="code-inline">multipart/form-data</code> avec un champ <code class="code-inline">file</code> (JPG, PNG ou WebP, max 8 Mo).
curl -X POST "https://hou.la/api/manager/link/abc123/og-image" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@my-og-image.jpg"200 OK
{
"ogImageUrl": "https://r2.hou.la/og-images/user123/link456/abc.webp",
"ogImageR2Key": "og-images/user123/link456/abc.webp"
}DELETE/manager/link/{id}/og-image
curl -X DELETE "https://hou.la/api/manager/link/abc123/og-image" \
-H "Authorization: Bearer YOUR_TOKEN"204 No Content
POST/manager/link/{id}/og-crawl
Re-crawle les meta Open Graph depuis l’URL de destination du lien. Utile pour mettre à jour les données OG après modification de la page cible.
curl -X POST "https://hou.la/api/manager/link/abc123/og-crawl" \
-H "Authorization: Bearer YOUR_TOKEN"200 OK
{
"ogTitle": "Page Title",
"ogDescription": "Page description from meta tags",
"ogImageUrl": "https://example.com/og-image.jpg",
"ogCrawlStatus": "success",
"ogCrawledAt": "2026-02-16T10:30:00.000Z"
}POST/manager/link/og-crawl-url
Crawle une URL sans créer de lien, pour prévisualiser les meta OG qui seront extraites.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | URL à crawler (ex : https://example.com) |
curl -X POST "https://hou.la/api/manager/link/og-crawl-url" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/my-page"
}'200 OK
{
"ogTitle": "My Page Title",
"ogDescription": "Description from meta tags",
"ogImageUrl": "https://example.com/image.jpg"
}GET/manager/link/{id}/og-preview
Retourne les meta OG stockées pour un lien (titre, description, image, statut du crawl).
curl -X GET "https://hou.la/api/manager/link/abc123/og-preview" \
-H "Authorization: Bearer YOUR_TOKEN"200 OK
{
"ogTitle": "Page Title",
"ogDescription": "Page description",
"ogImageUrl": "https://r2.hou.la/og-images/user/link/abc.webp",
"ogCrawlStatus": "manual",
"ogCrawledAt": "2026-02-16T10:30:00.000Z"
}Le Smart Routing permet de définir des règles de redirection conditionnelle sur vos liens. Chaque règle contient un libellé, une URL de destination, des conditions (pays, appareil, langue, navigateur, OS, première visite) et un poids optionnel pour l'A/B testing.
GET/link/{linkId}/rules
curl -X GET "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
[
{
"id": "rule-1",
"linkId": "abc123",
"label": "Visiteurs français mobile",
"destinationUrl": "https://example.fr/page-fr",
"matchType": "all",
"priority": 0,
"weight": 100,
"isActive": true,
"conditions": [
{
"field": "country",
"operator": "equals",
"value": "FR"
}
]
}
]POST/link/{linkId}/rules
| Paramètre | Type | Requis | Description |
|---|---|---|---|
label | string | Oui | Libellé de la règle (max 100 caractères) |
destinationUrl | string | Oui | URL de destination si la règle correspond |
matchType | string | Non | all (défaut) = toutes les conditions, any = au moins une |
weight | number | Non | Poids pour l'A/B testing (0-100). Si des règles ont des poids, le trafic est réparti proportionnellement. |
isActive | boolean | Non | Activer/désactiver la règle (défaut: true) |
conditions | array | Oui | Liste de conditions (voir ci-dessous) |
| Paramètre | Type | Valeurs possibles |
|---|---|---|
field | string | country, continent, device, os, browser, language, referrer, social_media, day_of_week, hour, date_range, is_bot, is_first_visit |
operator | string | equals, not_equals, contains, not_contains, in, not_in, starts_with, greater_than, less_than, between, not_between |
value | string | Valeur à comparer (ex: FR, mobile, true) |
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Visiteurs français mobile",
"destinationUrl": "https://example.fr/page-fr",
"matchType": "all",
"conditions": [
{ "field": "country", "operator": "equals", "value": "FR" },
{ "field": "device", "operator": "equals", "value": "mobile" }
]
}'Pour répartir le trafic entre plusieurs destinations, créez des règles sans conditions mais avec des poids. Le trafic sera distribué proportionnellement aux poids définis.
# Règle A - 70% du trafic
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Variante A",
"destinationUrl": "https://example.com/page-a",
"weight": 70,
"conditions": []
}'
# Règle B - 30% du trafic
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Variante B",
"destinationUrl": "https://example.com/page-b",
"weight": 30,
"conditions": []
}'PATCH/link/{linkId}/rules/{ruleId}
Mêmes paramètres que la création. Seuls les champs envoyés sont modifiés.
DELETE/link/{linkId}/rules/{ruleId}
PUT/link/{linkId}/rules/reorder
curl -X PUT "https://hou.la/api/link/abc123/rules/reorder" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"ruleIds": ["rule-2", "rule-1", "rule-3"]
}'Les webhooks vous permettent de recevoir des notifications HTTP en temps réel lorsque des événements se produisent sur vos liens ou pages Link-in-Bio. Au lieu de poll l'API régulièrement, configurez un webhook et recevez les données directement sur votre serveur.
profile.visited et profile.link_clicked pour les pages Link-in-Bio.| Événement | Description |
|---|---|
link.clicked | Un lien a été cliqué |
link.created | Un lien a été créé |
link.updated | Un lien a été modifié |
link.deleted | Un lien a été supprimé |
link.health_changed | L'état de santé d'un lien a changé (ex: URL cassée) |
link.safety_changed | Le statut de sécurité d'un lien a changé |
link.expired | Un lien éphémère a expiré |
link.password_attempt | Tentative d'accès à un lien protégé par mot de passe |
profile.visited | Une page Link-in-Bio a été visitée |
profile.link_clicked | Un lien a été cliqué sur une page Link-in-Bio |
POST/manager/webhook
| Paramètre | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Nom descriptif (max 100 caractères) |
url | string | Oui | URL de destination (HTTPS requis en production, max 2048 caractères) |
events | string[] | Oui | Liste des événements à écouter (au moins 1) |
linkId | string | Non | UUID du lien spécifique à surveiller |
tagId | string | Non | UUID du tag pour filtrer les événements |
batchSize | number | Non | Taille du batch (1-100, défaut: 1 = pas de batching) |
batchDelayMs | number | Non | Délai max avant envoi du batch en ms (0-60000, défaut: 0) |
samplingRate | number | Non | Pourcentage d'événements à envoyer (1-100, défaut: 100) |
anonymizeIp | boolean | Non | Anonymiser l'IP dans les payloads (RGPD) |
excludeGeoCity | boolean | Non | Exclure la ville des données de géolocalisation (RGPD) |
curl -X POST "https://hou.la/api/manager/webhook" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Analytics Integration",
"url": "https://my-app.com/webhooks/houla",
"events": ["link.clicked", "link.created"],
"anonymizeIp": true,
"excludeGeoCity": true
}'201 Created
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Analytics Integration",
"url": "https://my-app.com/webhooks/houla",
"events": ["link.clicked", "link.created"],
"enabled": true,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
"anonymizeIp": true,
"excludeGeoCity": true,
"batchSize": 1,
"samplingRate": 100,
"createdAt": "2025-01-15T10:30:00.000Z"
}secret n'est affiché qu'à la création. Conservez-le en lieu sûr pour vérifier les signatures.GET/manager/webhook
curl -X GET "https://hou.la/api/manager/webhook" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"GET/manager/webhook/{id}
curl -X GET "https://hou.la/api/manager/webhook/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"PATCH/manager/webhook/{id}
Tous les champs de création sont acceptés en paramètre (sauf secret).
curl -X PATCH "https://hou.la/api/manager/webhook/a1b2c3d4-..." \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Integration",
"events": ["link.clicked", "link.created", "link.deleted"]
}'DELETE/manager/webhook/{id}
curl -X DELETE "https://hou.la/api/manager/webhook/a1b2c3d4-..." \
-H "Authorization: Bearer YOUR_JWT_TOKEN"204 No Content
POST/manager/webhook/{id}/enable
POST/manager/webhook/{id}/disable
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../enable" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"POST/manager/webhook/{id}/test
Envoie un événement de test à l'URL du webhook pour vérifier que votre serveur répond correctement.
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../test" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"200 OK
{
"success": true,
"statusCode": 200,
"responseTimeMs": 142
}GET/manager/webhook/{id}/logs
| Paramètre | Type | Description |
|---|---|---|
page | number | Numéro de page (défaut: 1) |
limit | number | Résultats par page (défaut: 20, max: 100) |
success | boolean | Filtrer par succès (true) ou échec (false) |
curl -X GET "https://hou.la/api/manager/webhook/a1b2c3d4-.../logs?page=1&limit=10&success=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"POST/manager/webhook/{id}/regenerate-secret
Génère un nouveau secret de signature. L'ancien secret sera invalidé immédiatement.
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../regenerate-secret" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Chaque requête webhook inclut un en-tête X-Houla-Signature contenant une signature HMAC-SHA256. Vérifiez cette signature pour vous assurer que la requête provient bien de hou.la.
| En-tête | Description |
|---|---|
X-Houla-Signature | Signature HMAC-SHA256 du body |
X-Houla-Event | Type d'événement (ex: link.clicked) |
X-Houla-Delivery | UUID unique de la livraison |
X-Houla-Timestamp | Timestamp ISO 8601 de l'envoi |
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Dans votre handler Express
app.post('/webhooks/houla', (req, res) => {
const signature = req.headers['x-houla-signature'];
const event = req.headers['x-houla-event'];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
switch (event) {
case 'link.clicked':
console.log('Clic sur', req.body.link.shortUrl);
break;
case 'profile.visited':
console.log('Visite page bio', req.body.profile.slug);
break;
}
res.status(200).json({ received: true });
});Gérez des presets de pixels de retargeting pour pré-remplir automatiquement vos liens lors de la création.
GET /api/manager/pixel-preset
Authorization: Bearer <token>POST /api/manager/pixel-preset
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Mon site e-commerce",
"isDefault": true,
"fbPixelId": "123456789012345",
"googleTagId": "G-AB1CDE2FGH"
}| Paramètre | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Nom du preset (1-100 caractères) |
isDefault | boolean | Non | Définir comme preset par défaut pour les nouveaux liens |
fbPixelId | string | Non | Facebook Pixel ID (10-20 chiffres) |
googleTagId | string | Non | Google Tag ID (G-XXX, AW-XXX, DC-XXX ou UA-XXX) |
tiktokPixelId | string | Non | TikTok Pixel ID (commence par C, 11-31 caractères) |
PATCH /api/manager/pixel-preset/:id
Authorization: Bearer <token>DELETE /api/manager/pixel-preset/:id
Authorization: Bearer <token>Ajoutez vos propres domaines pour personnaliser vos liens courts. Cette fonctionnalité est réservée aux plans payants.
POST/domains
Ajoute un domaine personnalisé à votre compte. Vous devrez configurer un enregistrement DNS pour vérifier la propriété du domaine.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
domain | string | Oui | Le nom de domaine à ajouter (ex : links.monsite.com) |
curl -X POST "https://hou.la/api/domains" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"domain": "links.monsite.com"
}'201 Created
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "pending",
"verificationMethod": "cname",
"verificationToken": "houla-verify-abc123...",
"dnsVerified": false,
"sslConfigured": false,
"isActive": false,
"cnameTarget": "custom.hou.la",
"txtRecordName": "_houla-verify.links.monsite.com",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}GET/domains
curl -X GET "https://hou.la/api/domains" \
-H "Authorization: Bearer <token>"200 OK
[
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "active",
"verificationMethod": "cname",
"dnsVerified": true,
"sslConfigured": false,
"isActive": true,
"cnameTarget": "custom.hou.la",
"txtRecordName": "_houla-verify.links.monsite.com",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
]GET/domains/{id}
curl -X GET "https://hou.la/api/domains/a1b2c3d4-..." \
-H "Authorization: Bearer <token>"POST/domains/{id}/verify
Déclenche la vérification DNS du domaine. Le système vérifie que l’enregistrement CNAME ou TXT est correctement configuré.
curl -X POST "https://hou.la/api/domains/a1b2c3d4-.../verify" \
-H "Authorization: Bearer <token>"200 OK — Domaine vérifié avec succès
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "active",
"dnsVerified": true,
"dnsVerifiedAt": "2025-01-15T12:00:00.000Z",
"isActive": true
}PATCH/domains/{id}/verification-method
Change la méthode de vérification DNS du domaine (CNAME ou TXT).
| Paramètre | Type | Requis | Description |
|---|---|---|---|
method | string | Oui | Méthode de vérification : « cname » ou « txt » |
curl -X PATCH "https://hou.la/api/domains/a1b2c3d4-.../verification-method" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"method": "txt"
}'DELETE/domains/{id}
curl -X DELETE "https://hou.la/api/domains/a1b2c3d4-..." \
-H "Authorization: Bearer <token>"200 OK
{
"success": true
}Selon la méthode de vérification choisie, configurez l’un des enregistrements DNS suivants :
links.monsite.com. CNAME custom.hou.la._houla-verify.links.monsite.com. TXT "houla-verify-abc123..."| Code | Description |
|---|---|
| 400 | Requête invalide - Vérifiez les paramètres envoyés |
| 401 | Non autorisé - Clé API manquante ou invalide |
| 403 | Interdit - Clé API révoquée ou accès refusé |
| 404 | Non trouvé - La ressource demandée n'existe pas |
| 429 | Trop de requêtes - Limite de débit atteinte ou protection de sécurité activée |
| 500 | Erreur serveur - Contactez le support |
L'API applique des limites de requêtes selon le type d'authentification utilisé :
| Type d'utilisateur | Limite | TTL |
|---|---|---|
| Anonyme | 10 req/min | 60s |
| JWT authentifié | 100 req/min | 60s |
| Clé API | 1000 req/min | 60s |
| PAGES.API_DOC.RATE_LOGIN | 5 req/min | 60s |
Pour une utilisation optimale de l'API, nous recommandons de :
Le SDK houla-sdk vous permet d'intégrer rapidement l'API Hou.la dans vos projets Node.js/TypeScript.
npm install @houla/sdkimport { HoulaClient } from '@houla/sdk';
const client = new HoulaClient('houla_sk_xxxxxxxx');
// Créer un lien
const link = await client.createLink({
url: 'https://example.com/ma-page',
title: 'Mon lien'
});
console.log(link.shortUrl); // https://hou.la/xY7k9
// Créer un lien protégé par mot de passe
const protectedLink = await client.createLink({
url: 'https://example.com/secret',
title: 'Document confidentiel',
password: 'secret123'
});
// Lister les liens
const result = await client.getLinks({ page: 1, limit: 10 });
// Récupérer un lien par ID
const details = await client.getLinkById('abc123');
// Supprimer un lien
await client.deleteLink('abc123');
// ─── Webhooks ───
// Créer un webhook
const webhook = await client.createWebhook({
name: 'Mon webhook',
url: 'https://my-app.com/webhooks/houla',
events: ['link.clicked', 'link.created'],
anonymizeIp: true
});
console.log(webhook.secret); // À conserver !
// Lister les webhooks
const webhooks = await client.getWebhooks();
// Tester un webhook
const test = await client.testWebhook(webhook.id);
console.log(test.success, test.responseTimeMs);
// Consulter les logs
const logs = await client.getWebhookLogs(webhook.id, 1, 20);Pour toute question concernant l'API, contactez-nous à : hello@hou.la
Vous pouvez également consulter notre page de support pour plus d'informations.