Todos los artículos

Payments

Stripe como proveedor de DNS: cuando las buenas APIs se cruzan con malas ideas

5 de abril de 2025 · 9 min de lectura

Stripe como proveedor de DNS: cuando las buenas APIs se cruzan con malas ideas

Siempre he creído que la mejor manera de entender de verdad una tecnología es empujarla en direcciones absurdas y poco prácticas para ver qué se rompe. Inspirado por la afirmación de Corey Quinn de que Amazon Route 53 es en realidad una base de datos, me pregunté: ¿cuál sería lo contrario? Si el DNS puede usarse como base de datos, ¿cuál es la forma más absurda de implementar un servidor DNS?

Esa idea me llevó a este proyecto: usar los metadatos de Stripe como almacén de datos para un servidor DNS totalmente funcional.

Metadata

Cuando se trabaja con Stripe, los metadatos son un concepto muy importante. Simplifican enormemente la conexión entre el modelo de datos de Stripe y el de su propia aplicación. IDs de pedido, pertenencia a un programa de fidelización, IDs de un programa de referidos: Stripe no sabe ni le importa qué son estos conceptos en su aplicación, pero permite almacenar la información sobre los objetos centrales de Stripe para simplificar la unión entre ambos sistemas.

Se puede usar para etiquetar a un cliente con un ID interno:

"internal_user_id": "abcd1234"

O para registrar quién disparó una devolución:

"refunded_by": "support_team_bot"

En el fondo, los metadatos son un simple almacén clave-valor en el que puede guardar valores arbitrarios sobre los objetos centrales de Stripe. Customers, Subscriptions, Payment Intents, Products, etc.: todos los objetos centrales de Stripe disponen de un campo metadata, en el que puede guardar hasta 50 pares clave-valor por objeto. Aunque los metadatos son solo clave-valor, con un poco de aplanado y serialización (usando técnicas como JSON.stringify()), pueden adaptarse para almacenar registros DNS estructurados, si está dispuesto a violar varias buenas prácticas por el camino.

Los registros DNS son básicamente un mapeo de registro -> ubicación, lo que suena bastante a algo apropiado para un mapeo clave-valor.

Levantar el servidor DNS

Para el servidor DNS usaremos dns2. Esto nos permite escuchar consultas DNS y construir respuestas:

const dns2 = require('dns2')

class StripeDnsServer {
  constructor(options = {}) {
    this.stripeClient = options.stripeClient || stripe
    this.port = options.port || 5333
    this.address = options.address || '0.0.0.0'
    this.server = this._createServer()
  }

  _createServer() {
    return dns2.createServer({
      udp: true,
      handle: this._handleRequest.bind(this),
    })
  }

  start() {
    this.server.listen({
      udp: { port: this.port, address: this.address, type: 'udp4' },
      tcp: { port: this.port, address: this.address },
    })
    return this
  }
}

const dnsServer = new StripeDnsServer({ port: 5333 }).start()

Esto escuchará en el puerto 5333 (UDP y TCP) y delegará las peticiones entrantes al método _handleRequest, que aún tenemos que definir.

Estructurar los registros DNS para guardarlos en metadatos

Cuando se trabaja con DNS, lo habitual es tener registros como:

TipoNombreContenidoPreferencia
A@192.168.1.2
Awww192.168.1.3
MX@mail01.google.com10
CNAMEblogblog.wordpress.com.

Lo ideal sería representarlo en un formato estructurado tipo JSON:

{
  "@": { "A": ["192.168.1.2"] },
  "www": { "A": ["192.168.1.3"] },
  "blog": { "CNAME": ["blog.wordpress.com"] },
  "_mx": {
    "MX": [{ "preference": 10, "exchange": "mail01.google.com" }]
  }
}

Nuestro primer problema al mapear esto a los metadatos de Stripe es que tenemos algo más que una simple relación clave-valor. Los metadatos de Stripe no soportan estructuras complejas, así que arrays y estructuras anidadas no funcionan de serie. Es estrictamente clave-valor, donde el valor es una cadena (de hasta 500 caracteres).

Esa limitación no nos detiene, simplemente significa que tenemos que aplanar los datos nosotros mismos con JSON.stringify() antes de guardarlos y volver a expandirlos con JSON.parse() al recuperarlos. Guardaremos toda la estructura JSON como una cadena bajo una única clave de metadatos, dns_records:

metadata.dns_records: '{"blog":{"CNAME":["blog.example.net."]},"www":{"A":["192.168.1.3"]},"@":{"A":["192.168.1.2"],"MX":[{"priority":10,"exchange":"mail1.example.com."}]}}'

Para asociarlo con un dominio, añadimos otra clave de metadatos: dns_domain = example.com.

Guardar los datos en Stripe

¿Dónde adjuntamos estos metadatos? Los objetos Customer de Stripe son perfectos. Los podemos crear vía API sin necesidad de métodos de pago ni de datos financieros reales. Estamos usando Stripe, en la práctica, como una base de datos clave-valor (muy peculiar) y de capa gratuita.

const customer = await stripe.customers.create({
  name: 'DNS records for example.com',
  metadata: {
    dns_domain: 'example.com',
    dns_records: '{"www":{"A":["192.168.1.3"]}, ... }'
  }
});

La trampa de la Search API

Nuestro primer instinto para buscar registros podría ser usar la Search API de Stripe:

const customers = await stripe.customers.search({
  query: "metadata['dns_domain']:'${domain}'"
});

Parece bien. Sin embargo, hay un fallo crítico: la Search API de Stripe es eventually consistent. La documentación advierte explícitamente de no usarla para flujos de read-after-write. En condiciones normales, los datos se vuelven buscables en menos de un minuto, aunque pueden producirse retrasos de propagación durante incidencias.

Un posible retraso de un minuto entre crear o actualizar un registro DNS y que pueda resolverse no es nada bueno para un servidor DNS. Necesitamos un mecanismo de read-after-write fiable e inmediato.

El truco con la List API

Las operaciones list estándar sobre objetos de Stripe (como stripe.customers.list) son fuertemente consistentes para ciertos filtros. Aunque no se puede filtrar directamente por metadatos arbitrarios con list, sí se puede filtrar por campos estándar como email.

El truco es entonces: al crear el Customer, guardamos un identificador único para el dominio en el campo email. Usaremos dns@ seguido del nombre del dominio:

const customer = await stripe.customers.create({
  name: `DNS records for ${domain}`,
  email: `dns@${domain}`,
  metadata: { dns_domain: domain, dns_records: '...' }
});

Ahora podemos implementar la búsqueda con stripe.customers.list filtrando por email, lo que nos da consistencia inmediata.

Limitaciones (hay muchas)

  • Tamaño máximo de los metadatos: el límite de 500 caracteres por valor es una restricción dura. Las zonas complejas lo superan rápidamente.
  • Velocidad: es lento. Las llamadas a la API de Stripe son generalmente rápidas, pero para servidores DNS los tiempos de respuesta ultracortos son clave.
  • Sin caché: no hay caché DNS implementada en el servidor. Cada consulta golpea la API de Stripe.
  • Tipos de registro limitados: aquí solo se implementan A, CNAME y MX.
  • Tratamiento de errores y pruebas: es código de prueba de concepto. La gestión de errores, la validación de entradas y los tests son mínimos.

Si llegó a plantearse correr esto en producción, es un peligro para usted y para los demás.

¿De qué va exactamente todo esto?

¿Es una buena idea? No. ¿Es una opción viable si tiene prisa? Tampoco. ¿Es, en última instancia, una aplicación práctica de esta tecnología? Para nada: es el más bromista de los proyectos broma.

Este proyecto no es de calidad de producción (por favor, no lo use), pero sí muestra:

  • Cómo los metadatos de Stripe pueden retorcerse para almacenar datos estructurados.
  • Los límites del almacenamiento clave-valor plano y cómo sortearlos.
  • La importancia de entender los modelos de consistencia de las APIs (Search frente a List).
  • Que las "malas ideas" son a menudo las mejores herramientas didácticas.

El objetivo es mostrar que los metadatos pueden ser una tecnología increíblemente potente y flexible: si soportan un servidor DNS funcional (aunque pésimo), imagine los usos más prácticos que pueden aportar a sus propios flujos relacionados con pagos.

Seguir leyendo

Artículos relacionados

Ver todos los artículos →