Conversiones de Microsoft Ads sin la espera del developer-token
La API OfflineConversions de Microsoft bloquea las subidas de conversiones en producción detrás de una revisión del developer-token, la misma fricción de varias semanas que tiene la API de Google Ads. Beaconry enruta Microsoft a través del mismo broker central, así que te conectas con OAuth y empiezas a subir el mismo día. Esta es la arquitectura: atribución por msclkid, el modelo de direccionamiento por nombre de objetivo, los dos IDs de cuenta que necesita cada subida, y por qué Bing carga más gasto B2B del que sugiere su cuota de mercado.
La misma barrera que tiene Google Ads, un proveedor más allá
Si has leído el artículo sobre el developer-token de Google Ads, la forma de este problema te resulta familiar. Autenticarse contra la Microsoft Advertising Conversions API necesita dos credenciales separadas, y viven en lados opuestos de la línea de fricción.
- Un developer-token. Pertenece a quien escribió la integración. Identifica la aplicación ante Microsoft, regula la cuota de la API y es lo que Microsoft revisa cuando solicitas acceso a producción. Un token por integración, sin importar cuántas cuentas publicitarias lo usen.
- Una credencial OAuth. Pertenece a cada titular individual de una cuenta publicitaria. Indica qué cuenta de Microsoft está subiendo y a qué Customer ID. Se emite en segundos mediante el handshake OAuth estándar, sin cola de revisión.
El lado OAuth es instantáneo. El lado del developer-token es una solicitud escrita a Microsoft que describe tu caso de uso, tu manejo de datos y tu integración, seguida de un ida y vuelta con los revisores. La ventana realista está en el mismo rango de varias semanas que la de Google. Para una tienda pequeña de WordPress que solo quiere conversiones de Bing en sus informes, eso es una pausa de más de un mes antes de que la primera conversión pueda subirse.
La respuesta de Beaconry es la misma que usa para Google Ads: un Cloudflare Worker central (el broker) guarda el propio developer-token aprobado de Beaconry, y la instalación del cliente nunca lo ve. El cliente solo completa OAuth. Esa es toda la idea. El resto de este artículo trata sobre las partes del pipeline de Microsoft que no son una copia de Google, porque ahí es donde realmente viven los detalles de implementación.
En qué se diferencia Microsoft de Google en el cable
El patrón de broker es compartido, pero la API de Microsoft tiene cuatro diferencias concretas que el dispatcher tiene que manejar. Cada una es un lugar donde un port ingenuo de "simplemente clonar la ruta de Google" se rompería en silencio.
1. msclkid, no gclid
El identificador de clic de Google es gclid. El de Microsoft es msclkid (Microsoft Click ID). Llega de la misma manera, como un parámetro de URL en la landing page cuando un visitante hace clic en un anuncio de Microsoft, y el motor nl-data de Beaconry lo captura al aterrizar y lo persiste en la cookie first-party nl_ext junto a cada otra click-ID que rastrea. Del lado del servidor, el orden de lectura es primero el parámetro ?msclkid= de la solicitud en vivo, luego la cookie nl_ext:
public static function get_msclkid(): string {
if ( ! empty( $_GET['msclkid'] ) ) {
$id = sanitize_text_field( wp_unslash( (string) $_GET['msclkid'] ) );
if ( $id !== '' ) {
return $id;
}
}
// ... fall back to the nl_ext cookie's persisted msclkid
}Ese orden importa: el parámetro de URL es la señal más fresca (el visitante está ahora mismo en la landing page del anuncio), y la cookie es el respaldo duradero para conversiones que ocurren en una vista de página posterior dentro de la misma sesión.
2. Las conversiones se direccionan por NOMBRE de objetivo, no por un ID numérico
Esta es la diferencia que de verdad hace que Microsoft sea más fácil de configurar que Google. En la ruta de Google Ads, mapeas cada evento a un ConversionAction-ID numérico, un número opaco que copias de la interfaz de Google Ads. Microsoft direcciona las conversiones por su ConversionName: el nombre de objetivo legible que escribiste en la interfaz de Bing Ads bajo "Conversion goals". Así que, en lugar de pegar 1234567890 por slot, escribes Purchase o Newsletter signup, exactamente como aparece en Microsoft Advertising. La cadena viaja por el cable como ConversionName y Microsoft la empareja del lado del servidor.
La contrapartida: un error tipográfico en el nombre del objetivo falla en silencio. No hay un ID numérico contra el que validar, así que si el nombre en Beaconry no coincide carácter por carácter con el nombre en la interfaz de Bing Ads, Microsoft acepta la subida y la atribuye discretamente a nada. La solución es operativa, no arquitectónica: copia el nombre, no lo vuelvas a escribir.
3. Dos IDs de cuenta por subida, no uno
Google necesita un único Customer ID por subida. Microsoft necesita dos: un CustomerId y un CustomerAccountId. El primero identifica al cliente (la entidad a nivel de manager), el segundo identifica la cuenta publicitaria específica bajo él. Ambos son numéricos de 6 a 12 dígitos, ambos los envía el broker como cabeceras en cada subida, y un dispatch no está "listo" hasta que ambos estén almacenados:
public static function is_dispatch_ready(): bool {
return self::is_connected()
&& (string) BCNR_Settings::get( 'microsoft_ads_customer_id' ) !== ''
&& (string) BCNR_Settings::get( 'microsoft_ads_account_id' ) !== '';
}Microsoft no tiene un concepto login_customer_id separado como lo tiene Google Ads para escenarios de cuenta de manager (MCC). El CustomerId asume ese rol directamente, lo que es un campo menos del que preocuparse, aunque sea un ID más que Google en total.
4. Sin revoke de refresh-token documentado
Google expone un endpoint de revoke de token, así que desconectarse mata el grant de inmediato. Microsoft no publica un equivalente. Por eso la desconexión de Beaconry hace lo máximo que puede: hace POST a /microsoft/disconnect para que el broker borre su refresh-token almacenado del KV, luego descarta el bearer guardado localmente y toda la configuración de IDs y objetivos en WordPress. El propio Microsoft caduca el refresh-token del lado del servidor tras 90 días de inactividad. Un cliente que quiera el grant fuera ahora mismo puede revocar la app de Beaconry desde account.microsoft.com/privacy/app-access, lo que mata la sesión AAD del lado de Microsoft. El texto de desconexión del plugin apunta ahí por exactamente esa razón.
Los ocho slots de objetivos de conversión
Beaconry mapea un conjunto fijo de eventos canónicos a objetivos de conversión de Microsoft. Hay ocho slots, y el valor de cada slot es la cadena ConversionName que configuraste en la interfaz de Bing Ads:
public const GOAL_SLOTS = [
'purchase',
'add_to_cart',
'begin_checkout',
'lead',
'subscribe',
'schedule',
'signup',
'contact',
];Esos son purchase, add_to_cart, begin_checkout, lead, subscribe, schedule, signup y contact. Los cuatro de comercio (purchase, add_to_cart, begin_checkout, más el funnel implícito en el que se sitúan) cubren una tienda WooCommerce o EDD; los otros cuatro (lead, subscribe, schedule, signup, contact) cubren los funnels B2B y SaaS donde Microsoft Ads de verdad se gana el sueldo. Solo rellenas los slots para los que tienes objetivos. Un evento cuyo slot no tiene nombre de objetivo establecido se trata como "no deseado en este canal" y se omite en silencio, así que un sitio de generación de leads que nunca vende un producto simplemente deja en blanco los slots de comercio.
El conjunto de slots tiene deliberadamente la misma forma que los slots de etiquetas de Google Ads, de modo que el forwarder puede repartir un único evento canónico a ambos proveedores desde un solo mapa sin ramificación por proveedor.
El respaldo de atribución: msclkid O PII hasheada
Aquí está la parte del dispatcher que genera la mayoría de las conversiones del mundo real. El endpoint OfflineConversions de Microsoft quiere un MicrosoftClickId para atribuir una conversión al anuncio que la impulsó. Pero msclkid es un valor de cookie, y los valores de cookie no sobreviven para siempre: una ruta de varios días, una limpieza del almacenamiento del navegador o un dispositivo distinto entre el clic y la compra pueden perderlo. Si msclkid fuera estrictamente obligatorio, cada una de esas conversiones de ventana más larga se descartaría.
La propia especificación del objeto de datos OfflineConversion de Microsoft hace que MicrosoftClickId sea opcional cuando se suministra en su lugar un email hasheado o un teléfono hasheado, alimentando su pipeline de emparejamiento de enhanced-conversions. Beaconry implementa exactamente ese respaldo. La subida procede si tiene o bien una click-ID o bien al menos una clave de emparejamiento hasheada, y se omite solo cuando no tiene ninguna de las dos:
$msclkid = (string) ( $event['msclkid'] ?? '' );
$user = (array) ( $event['user_data'] ?? [] );
$has_user_id = ! empty( $user['em'] ) || ! empty( $user['ph'] );
if ( $msclkid === '' && ! $has_user_id ) {
return; // nothing to attribute on, silent skip
}Así que una compra de WooCommerce donde el msclkid del visitante se perdió tres días después del clic en el anuncio aún se sube, porque el checkout le pasó a Beaconry un email hasheado. La click-ID es la señal individual más fuerte, pero no es la única, y el dispatcher se niega a tirar una conversión perfectamente atribuible solo porque la cookie envejeció.
Qué va realmente por el cable
El objeto OfflineConversion que Beaconry ensambla es un subconjunto ajustado del esquema de Microsoft. Cada campo mapea a una clave de esquema documentada, y los campos que están vacíos se eliminan antes de enviar para que la API nunca vea una HashedEmailAddress en blanco o un MicrosoftClickId vacío:
$conversion = array_filter( [
'ConversionName' => $goal_name,
'ConversionTime' => gmdate( 'Y-m-d\TH:i:s\Z', $ts ),
'ConversionValue' => $value,
'ConversionCurrencyCode' => strtoupper( $currency ),
'MicrosoftClickId' => $msclkid,
'HashedEmailAddress' => $user['em'] ?? '',
'HashedPhoneNumber' => $user['ph'] ?? '',
], /* drop empty + null */ );Dos detalles que salieron de leer la especificación de Microsoft en lugar de adivinar a partir de la ruta de Google:
- ConversionTime es ISO 8601 con un marcador UTC explícito. El formato es
yyyy-mm-ddThh:mm:ssZ, generado congmdate(...'Z')de modo que sea inequívocamente UTC. Una cadena ingenua de hora local aquí pondría las conversiones en la ventana de atribución equivocada. - TransactionId NO está en el esquema OfflineConversion. Una versión temprana del dispatcher lo arrastraba por costumbre; Microsoft o bien descarta los campos desconocidos o los rechaza, así que se eliminó. La idempotencia a nivel de pedido que TransactionId habría proporcionado la maneja aguas arriba el
event_idestable por pedido de Beaconry en su lugar, antes de que el evento siquiera llegue al dispatcher de Microsoft.
La moneda se pasa a mayúsculas, el valor es un float, y todo el objeto pasa por array_filter de modo que la payload del cable contiene solo las claves que tienen datos reales.
La ruta de subida, de extremo a extremo
Juntando el broker y el dispatcher, una única subida de conversión de Microsoft corre así:
- Un evento de funnel se dispara del lado del servidor (un hook de compra de WooCommerce, un envío de formulario, una venta de EDD). El forwarder lo enruta al canal de Microsoft con una de las ocho clases de objetivo.
- El dispatcher comprueba
is_dispatch_ready()(bearer + ambos IDs almacenados), busca el ConversionName para esa clase de objetivo, y aborta en silencio si falta alguno. - Comprueba el respaldo de atribución: msclkid o em/ph hasheado, si no, omitir.
- Construye el objeto OfflineConversion de arriba y lo hace POST a la ruta
/microsoft/uploaddel broker con el bearer firmado con HMAC del sitio en la cabecera Authorization. - El broker lee el refresh-token almacenado para ese sitio, acuña un access-token de Microsoft fresco, adjunta el developer-token central más las cabeceras por llamada CustomerId y CustomerAccountId, y retransmite a
campaign.api.bingads.microsoft.com. - La llamada es fire-and-forget:
blocking => false, así que la solicitud original del navegador (el checkout, el envío del formulario) retorna de inmediato. Los resultados afloran en el contador/microsoft/quotadel broker, que el panel de estado lee.
El WordPress del cliente guarda solo el JWT del bearer, los dos IDs numéricos de cuenta y las cadenas de nombres de objetivo. Cada secreto real, el client secret de OAuth de Microsoft, el developer-token, los refresh y access tokens, vive en el almacén de secretos de Wrangler del broker y en su KV, indexado por un site_id opaco horneado en el bearer. El propio flujo OAuth corre a través del endpoint /common/ v2.0 de Microsoft con el scope msads.manage offline_access, de modo que tanto las cuentas AAD de trabajo o centro educativo como las cuentas personales de Microsoft (que es lo que usan la mayoría de los clientes de Bing Ads) pueden conectarse sin ser forzados a través de un tenant específico.
Por qué Bing carga gasto B2B desproporcionado a su cuota de mercado
Es tentador saltarse Microsoft Ads porque la cuota de búsqueda global de Bing parece pequeña al lado de Google. Ese razonamiento malinterpreta quién está realmente en Bing. Microsoft Search es el predeterminado en un lugar muy específico y muy valioso: la base instalada de Office 365 y Microsoft 365, Edge en flotas gestionadas de Windows 11 y escritorios corporativos donde TI nunca cambió el predeterminado. Esa población se inclina fuertemente hacia los responsables de decisiones de negocio, las compras y los compradores de SaaS que gastan con una tarjeta de empresa.
Las consecuencias prácticas para un anunciante B2B o SaaS:
- Alcance B2B desproporcionado. En campañas orientadas a negocio, Microsoft Ads suele capturar una porción del tráfico de pago muy por encima de lo que implica la cuota de búsqueda bruta, porque la composición de la audiencia es exactamente el segmento de negocio de alta intención.
- Menos competencia, a menudo CPAs más bajos. Menos anunciantes pujan en Bing que en Google, así que la misma keyword puede cerrarse a un coste por adquisición más bajo. Eso solo aparece en tus informes si las conversiones se están subiendo de verdad, que es todo el sentido de poner la API a funcionar.
- La sindicación de socios de búsqueda es alcance gratis. Microsoft Ads también sirve en Yahoo, Ecosia y DuckDuckGo a través de acuerdos de sindicación de búsqueda. Las conversiones de esos clics de socios cargan msclkid de la misma manera, así que no hace falta ningún manejo especial en el dispatcher.
Una nota sobre LinkedIn, porque la propiedad confunde a la gente: Microsoft posee LinkedIn, pero Microsoft Ads y la LinkedIn Conversions API son dos plataformas separadas con dos slots separados en Beaconry. Poseer ambas no las fusiona. Si corres campañas en las dos, configuras las dos; los eventos fluyen a la que esté cableada.
Conclusión
Microsoft Ads tiene la misma barrera del developer-token que Google Ads, y Beaconry se la salta de la misma manera: un broker central guarda el token aprobado, te conectas con OAuth, subes el mismo día. Las partes que vale la pena conocer son las diferencias, msclkid en lugar de gclid, objetivos de conversión direccionados por nombre en lugar de por un ID numérico, dos IDs de cuenta por subida en lugar de uno, y un respaldo de atribución de click-ID-o-PII-hasheada que rescata las conversiones de ventana más larga que una regla estricta de msclkid descartaría. Nada de eso compromete la separación de privacidad: el refresh-token del cliente nunca sale de WordPress, el developer-token de Beaconry nunca sale del broker. Y para una tienda B2B o SaaS, el canal vale el cableado, porque la audiencia de Bing carga más intención de negocio de la que su cuota de mercado jamás sugeriría.