ChartQuery

Descargas en lote con webhooks

Envía múltiples trabajos de descarga de video simultáneamente y recibe notificaciones webhook cuando cada uno termine — sin necesidad de consultas periódicas.

Descripción general

En lugar de consultar el estado, puedes pasar una webhook_url al enviar un trabajo. HuntAPI enviará el resultado por POST a tu URL tan pronto como la descarga esté lista. Este tutorial muestra:

  1. Enviar un lote de trabajos con una URL de webhook
  2. Recibir y procesar el payload del webhook

Prerrequisitos

  • Una clave de API de HuntAPI — consigue una en app.huntapi.com
  • Un endpoint HTTP accesible públicamente (usa ngrok para pruebas locales)
  • Instala las dependencias para tu lenguaje:
pip install requests flask
npm install express @types/express

Extensión curl habilitada (activa por defecto).

Sin dependencias adicionales — usa net/http (Go 1.18+).

Sin dependencias adicionales — usa com.sun.net.httpserver (Java 6+).

Sin dependencias adicionales — usa ASP.NET Minimal API (.NET 6+).

# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
axum = "0.7"

Pasos

Envía un lote de trabajos con URL de webhook

Pasa tu endpoint accesible públicamente como webhook_url. Cada trabajo es independiente; HuntAPI llamará al webhook cuando esté listo.

import requests

API_KEY     = "YOUR_API_KEY"
WEBHOOK_URL = "https://tuservidor.ejemplo.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"Enviado: {job_id}")

print(f"\n{len(job_ids)} trabajos enviados. Esperando webhooks...")
const API_KEY     = "YOUR_API_KEY";
const WEBHOOK_URL = "https://tuservidor.ejemplo.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(`Enviado: ${job_id}`);
}
console.log(`\n${jobIds.length} trabajos enviados. Esperando webhooks...`);
<?php
$apiKey     = "YOUR_API_KEY";
$webhookUrl = "https://tuservidor.ejemplo.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 "Enviado: {$data['job_id']}\n";
}
echo count($jobIds) . " trabajos enviados. Esperando 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://tuservidor.ejemplo.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("Enviado:", jobID)
    }
    fmt.Printf("%d trabajos enviados. Esperando 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://tuservidor.ejemplo.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("Enviado: " + jobId);
}
using System.Net.Http;
using System.Text.Json;

var apiKey     = "YOUR_API_KEY";
var webhookUrl = Uri.EscapeDataString("https://tuservidor.ejemplo.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($"Enviado: {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://tuservidor.ejemplo.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!("Enviado: {}", data["job_id"].as_str().unwrap_or(""));
    }
    Ok(())
}

Crea un receptor de webhooks

HuntAPI enviará un cuerpo JSON por POST a tu endpoint con job_id, status y download_url cuando el trabajo se complete.

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 recibido — Trabajo: {job_id}, Estado: {status}")
    if status == "done" and download_url:
        print(f"  URL de descarga: {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 recibido — Trabajo: ${job_id}, Estado: ${status}`);

  if (status === "done" && download_url) {
    console.log(`  URL de descarga: ${download_url}`);
  }

  res.json({ received: true });
});

app.listen(3000, () => console.log("Receptor de webhooks en :3000"));
<?php
// webhook.php — colocar en un servidor público

$payload     = json_decode(file_get_contents("php://input"), true);
$jobId       = $payload["job_id"] ?? "";
$status      = $payload["status"] ?? "";
$downloadUrl = $payload["download_url"] ?? "";

error_log("Webhook recibido — Trabajo: {$jobId}, Estado: {$status}");

if ($status === "done" && $downloadUrl) {
    error_log("  URL de descarga: {$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 recibido — Trabajo: %v, Estado: %v\n", jobID, status)

        if status == "done" && downloadURL != nil {
            fmt.Println("  URL de descarga:", downloadURL)
        }

        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"received":true}`))
    })
    fmt.Println("Receptor de webhooks en :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 recibido — Trabajo: %s, Estado: %s%n", jobId, status);
            if ("done".equals(status) && !downloadUrl.isEmpty())
                System.out.println("  URL de descarga: " + downloadUrl);

            var resp = "{\"received\":true}".getBytes();
            exchange.sendResponseHeaders(200, resp.length);
            exchange.getResponseBody().write(resp);
            exchange.close();
        });
        server.start();
        System.out.println("Receptor de webhooks en :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 recibido — Trabajo: {jobId}, Estado: {status}");
    if (status == "done" && downloadUrl != null)
        Console.WriteLine($"  URL de descarga: {downloadUrl}");

    return Results.Json(new { received = true });
});

Console.WriteLine("Receptor de webhooks en :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 recibido — Trabajo: {}, Estado: {}", job_id, status);
    if status == "done" && !download_url.is_empty() {
        println!("  URL de descarga: {}", 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!("Receptor de webhooks en :3000");
    axum::serve(listener, app).await.unwrap();
}

Prueba localmente con ngrok

Durante el desarrollo, usa ngrok para exponer tu servidor local a internet y que HuntAPI pueda alcanzar tu webhook.

# Primero inicia tu servidor local, luego:
ngrok http 3000

Copia la URL https://xxxx.ngrok.io generada y úsala como tu parámetro webhook_url.

Tu endpoint de webhook debe devolver un código de estado 2xx en menos de 10 segundos, de lo contrario HuntAPI reintentará la entrega. Haz el procesamiento intensivo de forma asíncrona y confirma el webhook inmediatamente.

En esta página