TL;DR — Una petición HTTP desde VBA te devuelve exactamente dos cosas: un código de estado y un bloque de bytes. No te dice si los datos están al día, qué codificación de texto usan esos bytes ni si la petición llegó siquiera a tiempo al servidor. Eso es cosa tuya. Elige el objeto según la red que tiene que atravesar (
MSXML2.XMLHTTPusa la configuración y la caché del navegador del usuario;ServerXMLHTTPyWinHttpRequestte dan tiempos de espera y no tienen caché), comprueba tú mismo el estado, porque un 404 o un 500 nunca provoca un error, y decodifica los bytes a conciencia cuando el servidor envía algo que no sea UTF-8.
Dim http As Object
Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.setTimeouts 5000, 5000, 10000, 30000
http.Open "GET", "https://api.example.com/rates?base=EUR", False
http.send
If http.Status = 200 Then Debug.Print http.responseText
Este es el primer artículo de una serie sobre cómo traer datos de la web a Excel: aquí, la petición en sí; después, VBA JSON para convertir la respuesta en una estructura, y VBA web scraping para páginas que nunca se pensaron para que las leyera un programa. La idea de la serie es que una petición web te entrega texto, no datos. Entre el servidor y tus celdas hay tres traducciones que son responsabilidad tuya: de bytes a texto, de texto a estructura y de estructura a cuadrícula. Casi todos los fallos se producen en una de esas fronteras, no en la petición.
Lo que aprenderás
- El modelo mental: un estado y unos bytes
- Tres objetos, tres pilas de red
- Dos tipos de fallo: errores que saltan y estados que no
- Por qué un GET devuelve los datos de ayer
- Tiempos de espera, y por qué una petición puede congelar Excel
- Acentos ilegibles: decodificar tú mismo los bytes
- Enviar un POST con un cuerpo JSON
- Cadenas de consulta y claves de API
El modelo mental: un estado y unos bytes
Cuando llamas a http.send, VBA entrega la petición a una biblioteca de red de Windows y espera. Lo que vuelve es
poco: un número (Status, como 200 o 404), algunos encabezados y el cuerpo en forma de bytes. responseText no es
el cuerpo; es el cuerpo ya decodificado como texto a partir de una suposición. responseBody son los bytes en
bruto.
Quédate con esa imagen y los fallos raros dejan de ser raros. Datos desactualizados significan que respondió en su lugar una capa situada entre tú y el servidor. Texto ilegible significa que la suposición al decodificar fue errónea. Una macro que «ayer funcionaba» y ahora se queda colgada significa que la red se volvió más lenta y nada le dijo a la petición que se rindiera.
Tres objetos, tres pilas de red
Los tres objetos que la gente copia de los foros parecen intercambiables. No lo son: cada uno se apoya en una pila de red de Windows distinta, y esa pila decide cómo se comporta la petición en el equipo de tu usuario.
| Objeto | Pila de red | Caché | Proxy e inicio de sesión | Tiempos de espera |
|---|---|---|---|---|
MSXML2.XMLHTTP.6.0 |
WinINet (la pila que hay detrás de la antigua configuración del navegador) | sí, las respuestas GET pueden quedar en caché | usa automáticamente la configuración de Internet de Windows del usuario | no tiene método setTimeouts |
MSXML2.ServerXMLHTTP.6.0 |
WinHTTP | no | la configuración de proxy propia de WinHTTP, que a menudo está vacía; setProxy para definir uno |
setTimeouts |
WinHttp.WinHttpRequest.5.1 |
WinHTTP | no | igual que el anterior; SetProxy |
SetTimeouts, además de opciones como las versiones de TLS |
Esa tabla explica la incidencia de soporte más habitual: «en mi portátil funciona y en la oficina del cliente falla».
La oficina sale a la web a través de un proxy. XMLHTTP toma ese proxy de la configuración del usuario;
ServerXMLHTTP y WinHttpRequest no, así que no consiguen conectar mientras el navegador del mismo equipo funciona
sin problemas. Indícales el proxy de forma explícita:
http.setProxy 2, "proxy.company.local:8080" ' 2 = usar este proxy
Los tres se crean con CreateObject y no necesitan ninguna referencia, así que el mismo código funciona en Office de
32 y de 64 bits. Consulta VBA CreateObject para el enlace tardío (late binding) en
general.
Dos tipos de fallo: errores que saltan y estados que no
Una petición puede fallar en dos sitios, y VBA te lo comunica por dos canales distintos.
Los fallos de transporte provocan un error en tiempo de ejecución. Sin red, un nombre de servidor que no se
resuelve, un tiempo de espera agotado, un protocolo de enlace TLS que falla: send se detiene con un error en tiempo
de ejecución, normalmente un número negativo grande como -2147012889 (no se pudo resolver el nombre del servidor) o
-2147012894 (se agotó el tiempo de espera de la operación). Captúralos con On Error.
Los fallos HTTP no provocan nada. Un 404, un 401 porque la clave ha caducado, un 500 porque el servidor se ha
estropeado: send termina con normalidad, Status contiene el código y responseText contiene una página de error
o un JSON de error. El código que se salta la comprobación del estado escribe tan tranquilo «Unauthorized» o una
página de HTML en tu hoja.
Así que cada petición necesita las dos comprobaciones. Ponlas en una sola función y no llames nunca a send en
ningún otro sitio:
Function HttpGet(ByVal url As String) As String
Dim http As Object
Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.setTimeouts 5000, 5000, 10000, 30000
http.Open "GET", url, False
http.setRequestHeader "Accept", "application/json"
http.send ' aqui saltan los errores de transporte
If http.Status < 200 Or http.Status >= 300 Then
Err.Raise vbObjectError + 513, "HttpGet", _
"HTTP " & http.Status & " " & http.statusText & " for " & url
End If
HttpGet = http.responseText
End Function
Ahora los dos tipos de fallo llegan como un error en tiempo de ejecución con un mensaje legible, y quien llama a la función los gestiona en un solo sitio. Consulta control de errores en VBA para el patrón que la rodea.
Por qué un GET devuelve los datos de ayer
MSXML2.XMLHTTP pasa por WinINet, y WinINet mantiene una caché. Si la respuesta del servidor permite guardarla en
caché, o no dice nada al respecto, un GET repetido a la misma URL puede responderse desde la caché sin llegar siquiera
al servidor. La macro se ejecuta, el estado es 200 y los tipos de cambio son de esta mañana.
Tres salidas, de mejor a peor:
- Usa
ServerXMLHTTPoWinHttpRequest, que no tienen caché. - Si tienes que quedarte con
XMLHTTP(por cómo maneja el proxy), obliga a la caché a volver a preguntar al servidor enviando una fecha muy antigua:http.setRequestHeader "If-Modified-Since", "Sat, 01 Jan 2000 00:00:00 GMT". - Haz que cada URL sea única con un parámetro desechable, como
"&_=" & Format(Now, "yyyymmddhhnnss"). Funciona, pero algunas API rechazan los parámetros que no conocen.
La señal inequívoca de la caché es una petición que vuelve al instante con exactamente los mismos datos que la última vez, mientras que la misma URL en un navegador muestra algo más reciente.
Tiempos de espera, y por qué una petición puede congelar Excel
El False de http.Open "GET", url, False hace que la petición sea síncrona: VBA se queda esperando en esa
línea hasta que llega la respuesta. Mientras espera, Excel no puede redibujarse ni responder, y Windows marca la
ventana como No responde. Una API lenta se convierte en un Excel congelado; una caída, sin tiempo de espera, puede
dejarlo congelado mucho tiempo.
setTimeouts recibe cuatro valores en milisegundos: resolución de nombres, conexión, envío y recepción. Ajústalos a
cifras que encajen con la API. 5000, 5000, 10000, 30000 significa: ríndete si el servidor no se encuentra o no se
alcanza en cinco segundos, o si no ha respondido en treinta. XMLHTTP no tiene ese método, lo que es un motivo más
para preferir ServerXMLHTTP en todo aquello que un usuario tenga que esperar.
En un bucle con muchas peticiones, actualiza la barra de estado entre llamadas para que el
usuario vea el progreso. Existen las peticiones asíncronas (True como tercer argumento), pero sondearlas desde VBA
requiere un bucle con DoEvents y añade más formas de fallar de las que elimina; mantén las peticiones síncronas y
cortas.
Acentos ilegibles: decodificar tú mismo los bytes
responseText decodifica los bytes con el juego de caracteres que el servidor declara en su encabezado
Content-Type, y supone UTF-8 cuando el servidor no declara ninguno. La mayoría de las API modernas envían UTF-8 y
lo indican, y todo funciona. Un servidor antiguo que envía Windows-1252, ISO-8859-1 o Shift_JIS sin decirlo
llega como München o como signos de interrogación y recuadros.
Cuando ocurra, olvídate de responseText, toma los bytes en bruto y decodifícalos con el juego de caracteres
correcto:
Function BytesToText(ByVal bytes As Variant, ByVal charset As String) As String
With CreateObject("ADODB.Stream")
.Type = 1 ' binario
.Open
.Write bytes
.Position = 0
.Type = 2 ' texto
.Charset = charset ' "windows-1252", "iso-8859-1", "shift_jis"
BytesToText = .ReadText
.Close
End With
End Function
body = BytesToText(http.responseBody, "windows-1252")
Una trampa mientras depuras esto: no juzgues el texto en la ventana Inmediato. El editor de VBA solo puede mostrar
caracteres de la página de códigos del sistema de Windows, así que un texto japonés correcto aparece como ??? en un
Windows en español, y un texto en español correcto puede verse mal en uno japonés. Escribe la cadena en una celda; la
celda muestra la verdad.
Enviar un POST con un cuerpo JSON
Un POST es la misma llamada con un método, un tipo de contenido y un cuerpo:
http.Open "POST", "https://api.example.com/orders", False
http.setRequestHeader "Content-Type", "application/json"
http.send "{""sku"":""A-100"",""qty"":3}"
La trampa es construir ese cuerpo pegando números dentro de una cadena. VBA convierte un número en texto según la
configuración regional de Windows, así que en tu Windows en español (con la configuración de España; en México,
por ejemplo, se usa el punto), igual que en uno alemán o francés, "{""price"":" & 3.5 & "}" produce
{"price":3,5}, que no es JSON válido. La misma macro funciona en Nueva York y falla en Madrid. O conviertes los
números con Trim$(Str$(x)), que siempre usa el punto (consulta VBA Str), o, mejor aún,
construyes el cuerpo con una biblioteca JSON, como se muestra en VBA JSON.
Cadenas de consulta y claves de API
Los valores que van en una URL deben codificarse con porcentajes: un espacio, un ampersand o una letra con diéresis en
un parámetro de consulta rompe la petición o la cambia sin avisar. Excel 2013 y posteriores tienen una función para
ello, y codifica las letras acentuadas en UTF-8, así que Zürich se convierte en Z%C3%BCrich:
url = "https://api.example.com/search?city=" & _
Application.WorksheetFunction.EncodeURL(Range("B2").Value)
' B2 = Zurich & Geneva -> city=Zurich%20%26%20Geneva
Las claves de API van en un encabezado, normalmente http.setRequestHeader "Authorization", "Bearer " & apiKey. No
van en el código. Cualquiera que tenga el libro puede abrir el editor de VBA, y la contraseña de un proyecto VBA no es
una protección real. Lee la clave en tiempo de ejecución desde un archivo del perfil del usuario o desde una variable
de entorno con Environ, para poder compartir el libro sin compartir la clave.
La decisión de criterio: una función de petición, tres comprobaciones
La mayoría de las macros HTTP rotas no se equivocan con HTTP. Se saltan una de las tres comprobaciones que la
biblioteca no hará por ti: ¿llegó a tiempo?, ¿dijo que sí el servidor? y ¿está bien decodificado el texto? Así
que escribe una sola función de petición que fije los tiempos de espera, compruebe el estado y decodifique a
conciencia, y haz que sea el único sitio del proyecto que llama a send.
Como objeto, usa por defecto MSXML2.ServerXMLHTTP.6.0: sin caché que devuelva datos viejos y con tiempos de espera
reales. Cambia a MSXML2.XMLHTTP.6.0 solo cuando los usuarios estén detrás de un proxy que no puedas configurar, y en
ese caso añade el encabezado If-Modified-Since. Y si lo único que necesitas es traer la misma tabla en cada
actualización, sin ninguna lógica alrededor, plantéate Datos > De la web de Power Query antes de escribir una sola
línea de VBA.
Cómo ayuda ExcelMaster
Las macros de API fallan en los equipos de otras personas: un proxy que nunca has visto, un Windows configurado en otro idioma, un servidor que responde despacio los lunes por la mañana.
ExcelMaster escribe el código de la petición para el libro que tiene delante, comprueba el estado y el resultado decodificado antes de que nada llegue a tus celdas, y te muestra lo que la API devolvió de verdad cuando no coincide con lo que esperabas.
Preguntas frecuentes
¿Cómo hago una petición HTTP GET en Excel VBA?
Crea MSXML2.ServerXMLHTTP.6.0 con CreateObject, llama a .Open "GET", url, False y después a .send, comprueba
.Status y lee .responseText. No hace falta ninguna referencia.
¿Qué diferencia hay entre XMLHTTP y ServerXMLHTTP?
XMLHTTP usa WinINet: sigue la configuración de proxy del usuario, puede guardar en caché las respuestas GET y no
tiene ajuste de tiempo de espera. ServerXMLHTTP usa WinHTTP: sin caché y con tiempos de espera reales, pero puede
que tengas que definir un proxy con setProxy.
¿Por qué mi petición VBA devuelve datos antiguos?
MSXML2.XMLHTTP puede responder a un GET repetido desde la caché de WinINet. Cambia a ServerXMLHTTP, o envía una
fecha antigua en If-Modified-Since con setRequestHeader antes de send.
¿Por qué VBA no da error con un 404 o un 500?
Porque la petición en sí ha salido bien: el servidor respondió. Solo los fallos de transporte, como no tener red o
agotar el tiempo de espera, provocan un error en tiempo de ejecución. Comprueba .Status después de cada send.
¿Por qué el texto de la respuesta sale ilegible?
El servidor envió texto en una codificación que no declaró, y responseText supuso UTF-8. Decodifica
.responseBody con ADODB.Stream y el Charset correcto.
Probado en
Probado en: Excel 365 (Windows 11), VBA 7.1 — última verificación el 05/10/2026.
Guías relacionadas: VBA JSON · VBA web scraping · VBA CreateObject · Control de errores en VBA · VBA On Error · VBA Str · VBA Environ · VBA StatusBar
