8 min de lecturaIngeniería

Cómo acortar una URL en PHP con curl, Guzzle o WordPress

Acorta una URL en PHP con un bloque breve de curl, la misma llamada en Guzzle o Laravel, una versión para WordPress con wp_remote_post, además de reintentos y acortamiento masivo.

Ana Kowalska
Marketing solutions engineering
Cómo acortar una URL en PHP: un POST de curl que envía una URL de destino a una API de acortamiento y decodifica el enlace corto de la respuesta JSON, junto con las opciones de Guzzle y WordPress

Acortar una URL en PHP es un único POST HTTP. Envía el enlace largo a la API de un acortador con la extensión curl, pasa tu clave de API como token Bearer y extrae el enlace corto de la respuesta con json_decode. No hace falta ningún paquete de Composer, aunque Guzzle y el cliente HTTP de Laravel reducen la misma llamada a tres líneas.

La mayoría de los tutoriales de PHP sobre este tema son piezas de museo. Configuran urlshortener/v1, que Google retiró después de poner fin a la creación de enlaces nuevos en 2019, mientras que los enlaces goo.gl inactivos dejaron de funcionar el 25 de agosto de 2025, o utilizan el endpoint bit.ly v3, que desapareció hace años. El código siguiente apunta a una API actual.

Esta es la versión para PHP de la guía general sobre cómo acortar una URL. Si todavía no has elegido un servicio, el resumen de la API gratuita de acortamiento de URL explica la forma de la solicitud, el modelo de autenticación y los límites que damos por supuestos aquí. Los nombres de los campos son los de Elido; la estructura se puede adaptar.

La forma más rápida: un POST de curl

Guarda la clave en el entorno, codifica tú mismo el cuerpo y lee el código de estado antes de confiar en la carga útil:

<?php

$ch = curl_init('https://api.elido.app/v1/links');

curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('ELIDO_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'destination_url' => 'https://example.com/spring-sale?utm_source=newsletter',
    ]),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($body === false || $status >= 400) {
    throw new RuntimeException("shorten failed: HTTP {$status}");
}

$link = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $link['short_url']; // https://s.elido.me/ab12cd

Hay dos líneas que existen porque todo el mundo se encuentra con esos errores alguna vez. CURLOPT_RETURNTRANSFER tiene false como valor predeterminado, así que sin ella curl imprime el JSON directamente en la salida y te entrega true; esa es la verdadera explicación de la mayoría de las preguntas del tipo «mi respuesta está vacía». Además, CURLOPT_POSTFIELDS cambia a la codificación de formulario multipart en cuanto le pasas un array, por lo que json_encode es lo que convierte la solicitud en una solicitud JSON.

CURLINFO_RESPONSE_CODE también importa. curl no tiene ningún problema en devolver un cuerpo lleno de {"error": "unauthorized"} sin quejarse.

Un POST de curl en PHP que envía una URL de destino y un token Bearer al endpoint de enlaces del acortador, la API devuelve HTTP 201 con un short_url que json_decode lee y un bucle de reintento espera entre intentos ante respuestas 429 y 5xx

La misma llamada en Guzzle o Laravel

Si el proyecto ya incluye Guzzle, el código repetitivo se reduce:

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.elido.app',
    'timeout'  => 10,
    'headers'  => ['Authorization' => 'Bearer ' . getenv('ELIDO_API_KEY')],
]);

$response = $client->post('/v1/links', [
    'json'    => ['destination_url' => $destination],
    'headers' => ['Idempotency-Key' => hash('sha256', $destination)],
]);

$short = json_decode((string) $response->getBody(), true)['short_url'];

Guzzle lanza ClientException ante respuestas 4xx y ServerException ante respuestas 5xx de forma predeterminada, lo contrario del silencio de curl y de lo que hace fetch en JavaScript. Captúralas, o configura http_errors como false y comprueba el estado tú mismo.

En Laravel, el cliente HTTP envuelve Guzzle y te ofrece reintentos sin coste adicional:

$short = Http::withToken(config('services.elido.key'))
    ->timeout(10)
    ->retry(3, 200, throw: false)
    ->post('https://api.elido.app/v1/links', ['destination_url' => $destination])
    ->json('short_url');

Ese retry(3, 200) son tres intentos con una pausa de 200 ms. Basta para un fallo transitorio, pero no para un límite de solicitudes; esa es la siguiente sección.

Acortar enlaces dentro de WordPress

WordPress no usa curl directamente. Tiene wp_remote_post, que elige un transporte por ti y funciona en alojamientos donde curl está desactivado. Conéctalo a la publicación y guarda el resultado como metadatos de la entrada para que las plantillas y los feeds puedan leerlo:

add_action('publish_post', function (int $post_id): void {
    if (get_post_meta($post_id, '_elido_short_url', true)) {
        return; // already shortened
    }

    $response = wp_remote_post('https://api.elido.app/v1/links', [
        'timeout' => 10,
        'headers' => [
            'Authorization'   => 'Bearer ' . ELIDO_API_KEY,
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => 'post-' . $post_id,
        ],
        'body' => wp_json_encode(['destination_url' => get_permalink($post_id)]),
    ]);

    if (is_wp_error($response)) {
        error_log('shorten failed: ' . $response->get_error_message());
        return;
    }

    $link = json_decode(wp_remote_retrieve_body($response), true);
    update_post_meta($post_id, '_elido_short_url', $link['short_url']);
}, 10, 1);

ELIDO_API_KEY debe estar en wp-config.php, no en un archivo de plugin que termine en un repositorio público ni en una fila de opciones que cualquier administrador pueda leer. Fíjate en la clave de idempotencia: post-123 es estable, así que si el hook se ejecuta dos veces al volver a publicar, la segunda llamada devuelve el enlace que ya existe en lugar de crear otro.

Si prefieres no mantener el hook, la guía del acortador de WordPress compara esta opción con la del plugin y las alternativas sin código.

¿Quieres ejecutar estos fragmentos tal cual? Crea una clave en el plan gratuito, expórtala como ELIDO_API_KEY y el bloque de curl anterior funcionará sin cambios.

Reintentos, límites de solicitudes e idempotencia

El PHP desatendido, ya sea un trabajo cron, un trabajador de cola o un importador masivo, debe sobrevivir a un 429 y a un 5xx transitorio. También debe sobrevivir al caso incómodo en el que el POST llega a la API, pero la respuesta se pierde durante el regreso. Tu código ve un tiempo de espera agotado, vuelve a intentarlo y crea un segundo enlace corto para el mismo destino, a menos que una clave de idempotencia lo impida.

function shorten(string $destination, int $retries = 3): string
{
    $key = hash('sha256', $destination); // stable across retries and re-runs

    for ($attempt = 0; $attempt < $retries; $attempt++) {
        [$body, $status, $retryAfter] = post_link($destination, $key);

        if ($status === 429) {
            sleep(max(1, (int) $retryAfter));
            continue;
        }
        if ($status >= 500) {
            sleep(2 ** $attempt); // exponential backoff
            continue;
        }
        if ($status >= 400) {
            throw new RuntimeException("shorten failed: HTTP {$status} {$body}");
        }

        return json_decode($body, true, 512, JSON_THROW_ON_ERROR)['short_url'];
    }

    throw new RuntimeException("shorten failed after {$retries} attempts");
}

Un 401 o un 403 nunca se arreglarán por sí solos, así que se lanza una excepción en el primer intento en lugar de esperar tres veces. Un 429 espera tanto como indique el encabezado Retry-After. Solo los 5xx reciben el retroceso progresivo que duplica la espera. El análisis detallado de los límites de solicitudes y la idempotencia explica todo el razonamiento, incluido por qué un hash del destino suele ser una clave mejor que un UUID aleatorio cuando se puede volver a ejecutar el mismo lote.

Una trampa específica de PHP: sleep() dentro de una solicitud web mantiene ocupado un trabajador de php-fpm durante todo el tiempo. Los reintentos pertenecen a scripts de CLI, trabajos de cola o WP-Cron, no a la solicitud que renderiza una página.

Acortar en masa sin esperas en serie

Un bucle foreach que acorta 500 URL hace 500 viajes de ida y vuelta consecutivos. A 120 ms cada uno, es un minuto de espera pura. PHP no tiene un entorno de ejecución asíncrono, pero sí tiene HTTP simultáneo, y Pool de Guzzle mantiene un número fijo de solicitudes en curso:

use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

$requests = static function (array $urls) {
    foreach ($urls as $url) {
        yield new Request('POST', '/v1/links', [
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => hash('sha256', $url),
        ], json_encode(['destination_url' => $url]));
    }
};

$short = [];
$pool  = new Pool($client, $requests($urls), [
    'concurrency' => 8,
    'fulfilled'   => function ($response, $i) use (&$short, $urls) {
        $short[$urls[$i]] = json_decode((string) $response->getBody(), true)['short_url'];
    },
    'rejected'    => function ($reason, $i) use (&$short, $urls) {
        $short[$urls[$i]] = 'ERROR: ' . $reason->getMessage();
    },
]);

$pool->promise()->wait();
Tres rutas de PHP hacia el mismo endpoint de acortamiento: curl puro en un alojamiento compartido, Guzzle o Laravel en una aplicación y wp_remote_post dentro de WordPress, todas leyendo la clave de API del entorno

Usar la URL original como clave de los resultados hace visible cualquier fallo parcial y permite volver a ejecutarlo. Sin Composer, curl_multi_init hace el mismo trabajo con más gestión: añade manejadores, itera con curl_multi_exec y recopila cada respuesta. Empieza con una concurrencia de aproximadamente ocho y deja que los encabezados de respuesta te indiquen si debes reducirla.

Qué opción elegir

Adapta la herramienta al alojamiento. Alojamiento compartido sin Composer: el bloque de curl puro. Una aplicación Symfony o Laravel: Guzzle o el cliente HTTP, con los reintentos configurados una sola vez en el nivel del cliente. WordPress: wp_remote_post en un hook de publicación. Una importación nocturna de miles de enlaces: el grupo, con una clave de idempotencia estable para que volver a ejecutarlo no tenga coste.

Elijas la que elijas, cuatro cosas no cambian: la clave procede del entorno, curl recibe un tiempo de espera explícito, el código de estado se lee antes que el cuerpo y todo lo que pueda ejecutarse dos veces lleva una clave de idempotencia. Los equipos que superan el primer script suelen querer la API y los SDK o lo que la plataforma ofrece a los desarrolladores: clics, etiquetas, caducidad, webhooks para eventos de enlaces en lugar de sondeo.

Lee la serie central

Este artículo forma parte del clúster de ingeniería. Empieza por la guía de la API gratuita de acortamiento de URL para conocer la estructura del endpoint y la autenticación; después, consulta el artículo sobre límites de solicitudes e idempotencia para comportarte correctamente bajo carga. La referencia actual es la documentación de la API, y si heredaste enlaces goo.gl, la alternativa a Google URL Shortener explica adónde van ahora.

Relacionado en el blog

Preguntas frecuentes

¿Cómo acorto una URL en PHP?

Envía la URL larga mediante POST a la API de un acortador con curl, incluye tu clave de API como encabezado Bearer y el destino en un cuerpo JSON, luego usa json_decode para leer el campo short_url de la respuesta. Son aproximadamente quince líneas con curl_setopt_array, o tres con Guzzle o el cliente HTTP de Laravel si el proyecto ya usa uno.

¿Cómo acorto una URL en PHP sin una biblioteca externa?

Usa la extensión curl que viene con PHP. curl_init, curl_setopt_array, curl_exec y json_decode cubren todo el flujo sin una dependencia de Composer, algo importante en un alojamiento compartido donde no puedes ejecutar Composer. Configura CURLOPT_RETURNTRANSFER como true o curl_exec imprimirá la respuesta en lugar de devolverla.

¿La API de Google URL Shortener sigue funcionando en PHP?

No. Google dejó de aceptar enlaces nuevos mediante la API de URL Shortener en 2019 y retiró el servicio; además, los enlaces goo.gl que habían quedado inactivos dejaron de resolver el 25 de agosto de 2025. Cualquier tutorial de PHP basado en urlshortener/v1 es código obsoleto, y los ejemplos de bit.ly v3 de la misma época también desaparecieron.

¿Puedo acortar una URL dentro de WordPress con PHP?

Sí. Llama a wp_remote_post desde un hook que se ejecute al publicar y guarda el enlace corto devuelto con update_post_meta para que el resto del tema pueda leerlo. Mantén la clave de API en wp-config.php, no en un archivo del plugin ni en la base de datos, y comprueba is_wp_error en la respuesta porque WordPress devuelve un objeto WP_Error en lugar de lanzar una excepción.

¿Cómo acorto muchas URL a la vez en PHP?

Envía las solicitudes simultáneamente con un grupo limitado en lugar de usar un bucle foreach que espera cada respuesta. Pool de Guzzle con una concurrencia de aproximadamente ocho es la ruta más corta, curl_multi hace lo mismo sin Composer, y una Idempotency-Key derivada de cada URL evita que una nueva ejecución cree enlaces duplicados.

¿Por qué mi solicitud de PHP con curl a un acortador de URL devuelve 401 o una respuesta vacía?

Un 401 significa que falta el encabezado Authorization o está mal formado: debe leerse exactamente 'Authorization: Bearer YOUR_KEY' como una sola cadena dentro del array CURLOPT_HTTPHEADER. Un valor de retorno vacío suele significar que nunca se configuró CURLOPT_RETURNTRANSFER, por lo que el cuerpo pasó directamente a la salida y curl_exec devolvió true en su lugar.

Prueba Elido

Pega una URL, obtén un enlace corto

Sin registro. El enlace vive 30 días. Crea una cuenta para conservarlo.

Gratis, sin registro · 2 por día

Prueba Elido

Acortador de URL alojado en la UE: dominios personalizados, análisis profundo y API abierta. Plan gratuito - sin tarjeta de crédito.

Etiquetas
how to shorten a url in php
php url shortener
shorten url php curl
url shortener api php
wordpress shorten url php
bulk shorten urls php

Seguir leyendo