Québec, Canada

403-1381 1re Avenue

+1 581.849.27.96

bdgouthiere@gmail.com

Attendre une vidéo de 90 secondes sans bloquer votre serveur

Ou : l’art d’attendre un colis sans passer la journée derrière la porte.

La réponse courte : pour une génération qui dure plus d’une minute, ne gardez pas une connexion ouverte en attendant le résultat. Soumettez la tâche, rendez la main, et laissez l’API vous prévenir par webhook quand la vidéo est prête. Gardez tout de même un polling lent en filet de sécurité : un webhook peut se perdre, et les plateformes ne le renvoient que quelques fois, trois au plus chez WaveSpeed, deux de plus chez Runpod. Sur les API de génération vidéo, c’est la différence entre un service qui tient la charge et un service qui perd des clips déjà payés.

Les durées justifient la méthode. WaveSpeed annonce une quarantaine de secondes pour un clip WAN 2.2 en 480p, 150 en 720p, 68 pour Kling 3.0 standard, 110 pour Hailuo 2.3. Artificial Analysis mesure plus de trois minutes pour WAN 2.2 chez fal.ai. Une image Flux sort en quelques secondes et se contente d’une attente simple ; une vidéo, non. Or nginx, par exemple, coupe par défaut une requête mandatée restée sans réponse au bout de 60 secondes : une attente synchrone de deux minutes derrière un tel serveur échoue avant même que l’API ait fini.

Il existe trois façons d’attendre, et chaque plateforme les règle différemment. La documentation des webhooks de WaveSpeed, celle de Replicate et celle de Runpod donnent les délais exacts, résumés dans le tableau qui suit.

Pour qui : un développeur qui intègre la génération vidéo dans une application et ne veut ni bloquer ses serveurs, ni perdre un clip déjà facturé.

À partir de : 0,15 $ le clip WAN 2.2 de cinq secondes en 480p, 0,42 $ pour Kling 3.0 standard, facturés à la génération, webhook compris.

Pour démarrer : créer une clé sur WaveSpeed, exposer une adresse HTTPS pour recevoir les webhooks, et passer cette adresse à chaque soumission.

Webhook ou polling : les délais et les pièges, plateforme par plateforme

PlateformeAttente synchroneConservation du résultatWebhook : nouvelles tentativesSignature
WaveSpeedenviron 120 s, puis la tâche continuefichiers gardés 7 jours au plus3 au plus, réponse attendue sous 10 sHMAC SHA-256
fal.aiappel direct sans file, ou file avec attentemédias 7 jours au moins, réglablejusqu’à 31, pendant environ 1 heureED25519
Replicate60 s au maximumtout est effacé après 1 heureétat final seulement, dernière environ 1 minute après la finHMAC SHA-256
Runpod Serverless90 s par défaut, résultat gardé 1 minute30 minutes après la fin2 de plus, à 10 s d’intervallenon relevée

L’attente synchrone d’abord. Elle convient aux images et aux tests, jamais à la vidéo en production. Chez WaveSpeed, au-delà de la fenêtre d’environ deux minutes, la réponse revient avec le statut processing et un code 5004 : la tâche continue, et votre code doit savoir la reprendre. Chez Runpod, le résultat d’un appel synchrone n’est conservé qu’une minute : si la connexion tombe, le clip est perdu, mais facturé.

Le polling ensuite. C’est la méthode la plus simple et la plus robuste, à trois conditions. Espacer les interrogations : deux secondes au minimum selon WaveSpeed, dix ou quinze secondes suffisent largement pour une vidéo. Fixer un délai maximal généreux : sur le pipeline vidéo de ce site, la boucle renonçait au bout de dix minutes, alors que la file de Runpod retenait certains jours un clip neuf à dix minutes avant de le lancer. Les clips finissaient quand même, facturés, et il fallait aller les rechercher à la main. Le délai est passé à trente minutes. Enfin, enregistrer l’identifiant de chaque tâche avant d’attendre, pour pouvoir la reprendre après un redémarrage, tant que la plateforme garde le résultat : trente minutes chez Runpod, une heure chez Replicate.

Le webhook enfin, la bonne méthode pour la vidéo. Vous passez une adresse HTTPS à la soumission, par le paramètre webhook chez WaveSpeed, fal_webhook chez fal.ai, le champ webhook du corps chez Replicate et Runpod, et la plateforme y envoie le résultat quand il est prêt. Trois règles s’appliquent. Répondre vite, avant tout traitement : WaveSpeed attend une réponse en moins de dix secondes. Vérifier la signature, puisque l’adresse est publique et que n’importe qui peut y envoyer une fausse vidéo terminée. Et accepter les doublons : une nouvelle tentative peut livrer deux fois le même résultat, il faut donc dédoublonner par identifiant de tâche.

Voici la réception d’un webhook WaveSpeed en Go, signature vérifiée selon la méthode documentée :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Vérifie la signature d'un webhook WaveSpeed avant de traiter le clip
func signatureValide(r *http.Request, corps []byte, secret string) bool {
	id := r.Header.Get("webhook-id")
	horodatage := r.Header.Get("webhook-timestamp")
	parties := strings.SplitN(r.Header.Get("webhook-signature"), ",", 2)
	if id == "" || len(parties) != 2 || parties[0] != "v3" {
		return false
	}
	ts, err := strconv.ParseInt(horodatage, 10, 64)
	if ecart := time.Now().Unix() - ts; err != nil || ecart > 300 || ecart < -300 {
		return false // trop ancien : possible rejeu
	}
	// La clé est le secret sans son préfixe whsec_, sans décodage base64
	mac := hmac.New(sha256.New, []byte(strings.TrimPrefix(secret, "whsec_")))
	mac.Write([]byte(id + "." + horodatage + "."))
	mac.Write(corps)
	attendu := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(attendu), []byte(parties[1]))
}

func recevoir(w http.ResponseWriter, r *http.Request) {
	corps, err := io.ReadAll(r.Body) // le corps brut, jamais re-sérialisé
	if err != nil || !signatureValide(r, corps, os.Getenv("WAVESPEED_WEBHOOK_SECRET")) {
		http.Error(w, "signature invalide", http.StatusUnauthorized)
		return
	}
	w.WriteHeader(http.StatusOK) // répondre d'abord : WaveSpeed attend moins de 10 secondes
	go traiter(corps)            // téléchargement du clip et dédoublonnage en arrière-plan
}

Le secret se récupère une fois par l’API de WaveSpeed et se garde côté serveur. Reste le filet de sécurité : toutes les dix minutes, une petite tâche interroge l’API pour chaque génération soumise depuis plus longtemps que prévu et toujours sans webhook reçu. Avec trois tentatives au plus chez WaveSpeed, une coupure de votre serveur au mauvais moment suffit à perdre un avis de fin ; le polling de rattrapage le retrouve, tant que le fichier est encore conservé.

Le webhook, c'est la cloche qui sonne quand le travail est fini : inutile de retourner voir la machine toutes les deux secondes.

Si vous partez de zéro

Avant les webhooks, il faut une première intégration qui soumet une tâche et récupère le résultat. Le code complet en Python et en Go est dans l’intégration Python et Go.

Si vous hésitez sur la plateforme

fal.ai retente un webhook bien plus longtemps que Replicate, qui efface pourtant vos fichiers au bout d’une heure. Les autres différences sont dans le face-à-face fal.ai et Replicate.

Si vous faites tourner la vidéo sur votre propre GPU

Sur un worker serverless, les mêmes règles s’appliquent, avec des délais plus courts et une facture à la seconde. Le calcul entre les deux options est dans API ou worker loué à la seconde.