Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Stripe Is My DNS Provider Now: When Bad Ideas M...

Stripe Is My DNS Provider Now: When Bad Ideas Meet Good APIs

I've always believed the best way to really understand a technology is to push it in stupid, impractical directions and see what breaks. Inspired by Corey Quinn's claim that Amazon Route 53 is actually a database, I wondered: what's the opposite of that? If DNS can be used as a database, what's the most absurd way to implement a DNS server?

In this talk, we'll explore a project born from that curiosity: using Stripe metadata as the datastore for a fully functional DNS server.

We'll dive into the mechanics of Stripe's metadata feature - normally used for linking internal IDs - and see how far we can stretch it. We'll look at how to coerce a simple key-value store into handling structured DNS records, the challenges of latency and rate limits, and the sheer joy of building something completely useless but technically fascinating.

Expect a fun, lighthearted session that proves you can learn a lot about API design and infrastructure by building things that absolutely should not exist.

Avatar for Paul Conroy

Paul Conroy

January 19, 2026
Tweet

More Decks by Paul Conroy

Other Decks in Technology

Transcript

  1. Stripe Is My DNS Provider Now
 When Bad Ideas Meet

    Good APIs Paul Conroy / @conroyp
  2. From Dublin, Ireland Started playing with the web 30+ years

    ago (Notepad, Frontpage & Geocities!) CTO at Square1 conroyp.com / @conroyp Paul Conroy 👴 🌍 🇮🇪
  3. • The only AWS service with a public 100% SLA

    • No charges for data transfer anywhere inside of AWS or out to the internet • Free if you move between zones every ~12 hours • It works! Route 53 as a database?
  4. Stripe Metadata • Custom key-value pairs you define • Available

    on most core Stripe objects • Useful for reconciling distinct systems • Visible in the Dashboard • Searchable via API # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: “no", } })
  5. DNS • Translates names into destinations www.stripe.com -> 52.215.231.162 •

    Distributed • Hierarchical lookup • Fast response times
  6. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) • Look up metadata.dns_domain • Extract records from metadata • Format as DNS response
  7. DNS • Translates names into destinations www.stripe.com -> 52.215.231.162 •

    Distributed • Hierarchical lookup • Fast response times
  8. DNS • Translates names into destinations www.stripe.com -> 52.215.231.162 •

    Distributed • Hierarchical lookup • Fast response times
  9. { "@": { "A": ["54.76.53.164"] }, "www": { "CNAME": ["stripe.com"]

    }, "blog": { "CNAME": ["blog.wordpress.com"] }, "_mx": { "MX": [ { "preference": 10, "exchange": "mail01.google.com" } ] } } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) Nested Structure Flat Structure
  10. { "@": { "A": ["54.76.53.164"] }, "www": { "CNAME": ["stripe.com"]

    }, "blog": { "CNAME": ["blog.wordpress.com"] }, "_mx": { "MX": [ { "preference": 10, "exchange": "mail01.google.com" } ] } } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) Nested Structure Flat Structure
  11. Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata:

    { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })
  12. Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata:

    { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })
  13. Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata:

    { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })
  14. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  15. const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length

    === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  16. const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length

    === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  17. const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length

    === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  18. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  19. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  20. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ email: "[email protected]", name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })
  21. customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: {

    dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ email: "[email protected]", name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) async lookupDnsRecords(domain, subdomain) { const lookupEmail = `dns@${domain}`; const customers = await stripe.customers.list({ email: lookupEmail, limit: 1 }); ...
  22. The Server • Create a DNS server with dns2 •

    Retrieve record for domain ◦ Check if request is for sub-domain • JSON-decode metadata • Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = 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(); } ...
  23. The Server • Create a DNS server with dns2 •

    Retrieve record for domain ◦ Check if request is for sub-domain • JSON-decode metadata • Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = 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(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ...
  24. The Server • Create a DNS server with dns2 •

    Retrieve record for domain ◦ Check if request is for sub-domain • JSON-decode metadata • Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = 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(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ... async lookupDnsRecords(domain, subdomain) { const email = `dns@${domain}`; const customers = await stripe.customers.list({ limit: 1, email: email }); const dnsRecords = JSON.parse( customers.data[0].metadata.dns_records || '{}' ); // Check for exact match, wildcard if no exact match let recordSet = dnsRecords[subdomain]; if (!recordSet && subdomain !== '@') { recordSet = dnsRecords['*']; } return recordSet || null; }
  25. The Server • Create a DNS server with dns2 •

    Retrieve record for domain ◦ Check if request is for sub-domain • JSON-decode metadata • Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = 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(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ... async lookupDnsRecords(domain, subdomain) { const email = `dns@${domain}`; const customers = await stripe.customers.list({ limit: 1, email: email }); const dnsRecords = JSON.parse( customers.data[0].metadata.dns_records || '{}' ); // Check for exact match, wildcard if no exact match let recordSet = dnsRecords[subdomain]; if (!recordSet && subdomain !== '@') { recordSet = dnsRecords['*']; } return recordSet || null; } addRecordsToResponse(response, name, recordSet, type) { switch (type) { case Packet.TYPE.A: if (recordSet.A) { recordSet.A.forEach(ip => { this.addRecord(response, name, Packet.TYPE.A, { address: ip }); }); return true; } break; case Packet.TYPE.CNAME: if (recordSet.CNAME) { ...
  26. 🧑💻 User dig DNS Lookup CLI tool @badidea.conroyp.com DNS Server

    -p 53 Port to use example.com Domain to check
  27. • Slower than standard DNS • Incomplete records • Not

    handling ttls • But… It worked! delete this slide quickly if demo failed horribly... … but should it?
  28. • Slower than standard DNS • Incomplete records • Not

    handling ttls • But… It worked! delete this slide quickly if demo failed horribly... … but should it?
  29. • Is this a good idea? What exactly is the

    point? No. • But is it a viable option if you’re in a hurry?
  30. • Is this a good idea? What exactly is the

    point? No. • But is it a viable option if you’re in a hurry? Also No.
  31. • Is this a good idea? What exactly is the

    point? No. • But is it a viable option if you’re in a hurry? Also No. • But is it ultimately a practical use of this technology?
  32. • Is this a good idea? What exactly is the

    point? No. • But is it a viable option if you’re in a hurry? Also No. • But is it ultimately a practical use of this technology? Absolutely not - it is the jokiest of joke projects.
  33. • Is this a good idea? What exactly is the

    point? No. • But is it a viable option if you’re in a hurry? Also No. • But is it ultimately a practical use of this technology? Absolutely not - it is the jokiest of joke projects.
  34. It’s a silly idea, but… • We’ve learned about metadata

    in a practical way • We hit its storage constraints ◦ .. and found workarounds (serialisation) • We’ve worked with the search api ◦ .. and read the docs, finally.. 😬 • Practical, hands-on experience to take into our real-world projects
  35. customer = Stripe::Customer.create({ email: "[email protected]", metadata: { loyalty_program: "no", }

    }) # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: “no", } })
  36. customer = Stripe::Customer.create({ email: "[email protected]", metadata: { loyalty_program: "no", }

    }) # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: “no", } }) # Set value if customer spends > 5000 customer = Stripe::Customer.update( '{{CUSTOMER_ID}}', { metadata: { premium_customer: "yes", } } )
  37. Stripe Workflows • Stripe-hosted • Visual Workflow builder • Works

    across many Stripe products https://docs.stripe.com/workflows
  38. Stripe Workflows • Stripe-hosted • Visual Workflow builder • Works

    across many Stripe products https://docs.stripe.com/workflows
  39. Stripe Workflows • Stripe-hosted • Visual Workflow builder • Works

    across many Stripe products https://docs.stripe.com/workflows
  40. Developing in an AI age • Well-structured APIs • Predictable

    (helpful!) error messages • Simple to verify (sandboxes) • Current documentation
  41. • MetadNS Project Code
 https://github.com/conroyp/metadns • Stripe Metadata https://docs.stripe.com/metadata •

    Stripe Workflows https://docs.stripe.com/workflows • Route53, Amazon’s Premier Database https://www.lastweekinaws.com/blog/route-53-amazons-premier-database/
 • Using Google Sheets as a RESTful API https://www.conroyp.com/articles/using-google-sheets-json-rest-api Further Reading