Téléchargements en lot avec webhooks
Soumettez plusieurs jobs de téléchargement vidéo à la fois et recevez des notifications webhook quand chacun se termine — sans interrogation.
Vue d'ensemble
Au lieu d'interroger, vous pouvez passer un webhook_url lors de la soumission d'un job. HuntAPI POSTera le résultat sur votre URL dès que le téléchargement est prêt. Ce guide montre comment :
- Soumettre un lot de jobs avec une URL webhook
- Recevoir et vérifier le payload webhook
Prérequis
- Une clé API HuntAPI — obtenez-en une sur app.huntapi.com
- Un endpoint HTTP accessible publiquement (utilisez ngrok pour les tests locaux)
- Installez les dépendances pour votre langage :
pip install requests flasknpm install express @types/expressExtension curl activée (par défaut).
Aucune dépendance supplémentaire — utilise net/http (Go 1.18+).
Aucune dépendance supplémentaire — utilise com.sun.net.httpserver (intégré, Java 6+).
Aucune dépendance supplémentaire — utilise l'API minimale ASP.NET (.NET 6+).
# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
axum = "0.7"Étapes
Soumettre un lot de jobs avec une URL webhook
Passez votre endpoint accessible publiquement comme webhook_url. Chaque job est indépendant ; HuntAPI appellera le webhook quand il se termine.
import requests
API_KEY = "YOUR_API_KEY"
WEBHOOK_URL = "https://yourserver.example.com/webhooks/huntapi"
URLS = [
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
]
job_ids = []
for video_url in URLS:
r = requests.get(
"https://api.huntapi.com/v1/video/download",
headers={"x-api-key": API_KEY},
params={"url": video_url, "quality": "best", "webhook_url": WEBHOOK_URL},
)
job_id = r.json()["job_id"]
job_ids.append(job_id)
print(f"Soumis : {job_id}")
print(f"\n{len(job_ids)} jobs soumis. En attente des webhooks...")const API_KEY = "YOUR_API_KEY";
const WEBHOOK_URL = "https://yourserver.example.com/webhooks/huntapi";
const URLS = [
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
];
const jobIds: string[] = [];
for (const videoUrl of URLS) {
const params = new URLSearchParams({ url: videoUrl, quality: "best", webhook_url: WEBHOOK_URL });
const res = await fetch(`https://api.huntapi.com/v1/video/download?${params}`, {
headers: { "x-api-key": API_KEY },
});
const { job_id } = await res.json();
jobIds.push(job_id);
console.log(`Soumis : ${job_id}`);
}
console.log(`\n${jobIds.length} jobs soumis. En attente des webhooks...`);<?php
$apiKey = "YOUR_API_KEY";
$webhookUrl = "https://yourserver.example.com/webhooks/huntapi";
$urls = [
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
];
$jobIds = [];
foreach ($urls as $videoUrl) {
$params = http_build_query(["url" => $videoUrl, "quality" => "best", "webhook_url" => $webhookUrl]);
$ch = curl_init("https://api.huntapi.com/v1/video/download?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
$jobIds[] = $data["job_id"];
echo "Soumis : {$data['job_id']}\n";
}
echo count($jobIds) . " jobs soumis. En attente des webhooks...\n";package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
func submitJob(apiKey, videoURL, webhookURL string) string {
params := url.Values{"url": {videoURL}, "quality": {"best"}, "webhook_url": {webhookURL}}
req, _ := http.NewRequest("GET", "https://api.huntapi.com/v1/video/download?"+params.Encode(), nil)
req.Header.Set("x-api-key", apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]any
json.Unmarshal(body, &data)
return data["job_id"].(string)
}
func main() {
apiKey := "YOUR_API_KEY"
webhookURL := "https://yourserver.example.com/webhooks/huntapi"
urls := []string{
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
}
for _, u := range urls {
jobID := submitJob(apiKey, u, webhookURL)
fmt.Println("Soumis :", jobID)
}
fmt.Printf("%d jobs soumis. En attente des webhooks...\n", len(urls))
}import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import org.json.*;
var client = HttpClient.newHttpClient();
var apiKey = "YOUR_API_KEY";
var webhookUrl = "https://yourserver.example.com/webhooks/huntapi";
var urls = new String[]{
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
};
for (var videoUrl : urls) {
var encoded = URLEncoder.encode(videoUrl, StandardCharsets.UTF_8);
var wh = URLEncoder.encode(webhookUrl, StandardCharsets.UTF_8);
var url = "https://api.huntapi.com/v1/video/download?url=" + encoded + "&quality=best&webhook_url=" + wh;
var req = HttpRequest.newBuilder().uri(URI.create(url))
.header("x-api-key", apiKey).GET().build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
var jobId = new JSONObject(resp.body()).getString("job_id");
System.out.println("Soumis : " + jobId);
}using System.Net.Http;
using System.Text.Json;
var apiKey = "YOUR_API_KEY";
var webhookUrl = Uri.EscapeDataString("https://yourserver.example.com/webhooks/huntapi");
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
var urls = new[]
{
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
};
foreach (var videoUrl in urls)
{
var encoded = Uri.EscapeDataString(videoUrl);
var body = await client.GetStringAsync(
$"https://api.huntapi.com/v1/video/download?url={encoded}&quality=best&webhook_url={webhookUrl}");
var jobId = JsonDocument.Parse(body).RootElement.GetProperty("job_id").GetString();
Console.WriteLine($"Soumis : {jobId}");
}use reqwest::Client;
use serde_json::Value;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let api_key = "YOUR_API_KEY";
let webhook_url = "https://yourserver.example.com/webhooks/huntapi";
let urls = [
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2",
"https://www.youtube.com/watch?v=VIDEO_ID_3",
];
for video_url in &urls {
let data = client.get("https://api.huntapi.com/v1/video/download")
.header("x-api-key", api_key)
.query(&[("url", video_url), ("quality", &"best"), ("webhook_url", &webhook_url)])
.send().await?.json::<Value>().await?;
println!("Soumis : {}", data["job_id"].as_str().unwrap_or(""));
}
Ok(())
}Construire un récepteur webhook
HuntAPI POSTera un corps JSON sur votre endpoint avec job_id, status et download_url quand le job se termine.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhooks/huntapi", methods=["POST"])
def huntapi_webhook():
payload = request.get_json()
job_id = payload.get("job_id")
status = payload.get("status")
download_url = payload.get("download_url")
print(f"Webhook reçu — Job : {job_id}, Statut : {status}")
if status == "done" and download_url:
print(f" URL de téléchargement : {download_url}")
return jsonify({"received": True}), 200
if __name__ == "__main__":
app.run(port=3000)import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/huntapi", (req, res) => {
const { job_id, status, download_url } = req.body;
console.log(`Webhook reçu — Job : ${job_id}, Statut : ${status}`);
if (status === "done" && download_url) {
console.log(` URL de téléchargement : ${download_url}`);
}
res.json({ received: true });
});
app.listen(3000, () => console.log("Récepteur webhook sur :3000"));<?php
// webhook.php — placez ce fichier sur un serveur public
$payload = json_decode(file_get_contents("php://input"), true);
$jobId = $payload["job_id"] ?? "";
$status = $payload["status"] ?? "";
$downloadUrl = $payload["download_url"] ?? "";
error_log("Webhook reçu — Job : {$jobId}, Statut : {$status}");
if ($status === "done" && $downloadUrl) {
error_log(" URL de téléchargement : {$downloadUrl}");
}
http_response_code(200);
echo json_encode(["received" => true]);package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/webhooks/huntapi", func(w http.ResponseWriter, r *http.Request) {
var payload map[string]any
json.NewDecoder(r.Body).Decode(&payload)
jobID := payload["job_id"]
status := payload["status"]
downloadURL := payload["download_url"]
fmt.Printf("Webhook reçu — Job : %v, Statut : %v\n", jobID, status)
if status == "done" && downloadURL != nil {
fmt.Println(" URL de téléchargement :", downloadURL)
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"received":true}`))
})
fmt.Println("Récepteur webhook sur :3000")
http.ListenAndServe(":3000", nil)
}import com.sun.net.httpserver.*;
import java.net.InetSocketAddress;
import org.json.*;
public class WebhookServer {
public static void main(String[] args) throws Exception {
var server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext("/webhooks/huntapi", exchange -> {
var body = exchange.getRequestBody().readAllBytes();
var payload = new JSONObject(new String(body));
var jobId = payload.optString("job_id");
var status = payload.optString("status");
var downloadUrl = payload.optString("download_url");
System.out.printf("Webhook reçu — Job : %s, Statut : %s%n", jobId, status);
if ("done".equals(status) && !downloadUrl.isEmpty()) {
System.out.println(" URL de téléchargement : " + downloadUrl);
}
var resp = "{\"received\":true}".getBytes();
exchange.sendResponseHeaders(200, resp.length);
exchange.getResponseBody().write(resp);
exchange.close();
});
server.start();
System.out.println("Récepteur webhook sur :3000");
}
}using System.Text.Json;
var app = WebApplication.Create();
app.MapPost("/webhooks/huntapi", async (HttpContext ctx) =>
{
using var reader = new StreamReader(ctx.Request.Body);
var body = await reader.ReadToEndAsync();
var payload = JsonDocument.Parse(body).RootElement;
var jobId = payload.GetProperty("job_id").GetString();
var status = payload.GetProperty("status").GetString();
var downloadUrl = payload.TryGetProperty("download_url", out var d) ? d.GetString() : null;
Console.WriteLine($"Webhook reçu — Job : {jobId}, Statut : {status}");
if (status == "done" && downloadUrl != null)
Console.WriteLine($" URL de téléchargement : {downloadUrl}");
return Results.Json(new { received = true });
});
Console.WriteLine("Récepteur webhook sur :3000");
app.Run("http://0.0.0.0:3000");use axum::{extract::Json as AxumJson, routing::post, Router};
use serde_json::{json, Value};
async fn huntapi_webhook(AxumJson(payload): AxumJson<Value>) -> AxumJson<Value> {
let job_id = payload["job_id"].as_str().unwrap_or("");
let status = payload["status"].as_str().unwrap_or("");
let download_url = payload["download_url"].as_str().unwrap_or("");
println!("Webhook reçu — Job : {}, Statut : {}", job_id, status);
if status == "done" && !download_url.is_empty() {
println!(" URL de téléchargement : {}", download_url);
}
AxumJson(json!({ "received": true }))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/webhooks/huntapi", post(huntapi_webhook));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Récepteur webhook sur :3000");
axum::serve(listener, app).await.unwrap();
}Tester localement avec ngrok
Pendant le développement, utilisez ngrok pour exposer votre serveur local à internet afin que HuntAPI puisse atteindre votre webhook.
# Démarrez d'abord votre serveur local, puis :
ngrok http 3000Copiez l'URL https://xxxx.ngrok.io générée et utilisez-la comme paramètre webhook_url.
Votre endpoint webhook doit renvoyer un code 2xx dans les 10 secondes, sinon HuntAPI retentera la livraison. Faites le traitement lourd de manière asynchrone et accusez réception du webhook immédiatement.