diff --git a/src/content/blog/2026-12-10-web-monetization-open-payments-part-2-connecting-wallet.md b/src/content/blog/2025-12-10-web-monetization-open-payments-part-2-connecting-wallet.md similarity index 98% rename from src/content/blog/2026-12-10-web-monetization-open-payments-part-2-connecting-wallet.md rename to src/content/blog/2025-12-10-web-monetization-open-payments-part-2-connecting-wallet.md index 862e759e..8e3a7428 100644 --- a/src/content/blog/2026-12-10-web-monetization-open-payments-part-2-connecting-wallet.md +++ b/src/content/blog/2025-12-10-web-monetization-open-payments-part-2-connecting-wallet.md @@ -123,4 +123,4 @@ The incoming payment serves as a dedicated "bucket" into which we can stream mic ## Sending money -This article is already long enough, so let's dive into the fun part — actually executing a payment — in the next article! +This article is already long enough, so let's dive into the fun part — actually executing a payment — in the [next article](https://interledger.org/developers/blog/web-monetization-open-payments-part-3-sending-money)! diff --git a/src/content/blog/2026-04-16-web-monetization-open-payments-part-3-sending-money.md b/src/content/blog/2026-04-16-web-monetization-open-payments-part-3-sending-money.md new file mode 100644 index 00000000..3abe8bd9 --- /dev/null +++ b/src/content/blog/2026-04-16-web-monetization-open-payments-part-3-sending-money.md @@ -0,0 +1,216 @@ +--- +title: 'How the Web Monetization Extension works - Part 3: Sending money for real' +description: 'Explores the "how" of Web Monetization: the who, when, and how much to send, plus embedded content and monetization signaling.' +date: 2026-04-16 +slug: web-monetization-open-payments-part-3-sending-money +authors: + - Sid Vishnoi +author_urls: + - https://sidvishnoi.com?ref=ilf_engg_blog +tags: + - Open Payments + - Web Monetization +--- + +In [Part 1](https://interledger.org/developers/blog/web-monetization-open-payments-part-1-connecting-wallet/) of the series, we covered how to link your wallet to the extension and secure the access tokens required for future transactions. In [Part 2](https://interledger.org/developers/blog/web-monetization-open-payments-part-2-payment-sessions), we explored the receiving side: identifying a website's receiving wallet addresses via `` and calculating the amounts our wallet can pay into them. Now, we'll combine these elements to actually move money. + +Thanks to the Open Payments API, sending the payment is now actually the simplest step. This article focuses more on the extension's internal payment logic and flow. I'll assume you're already familiar with basic browser extension architecture, [Web Monetization](https://webmonetization.org/), and [Open Payments API](https://openpayments.dev/) from the previous articles. + +## Payment manager + +As we explored in the last article, the extension's background script creates a "payment session" for every `` element found on the website you're visiting. Each session also figures out a `minSendAmount` - the smallest unit your connected-wallet can send to it. Once this value is confirmed, the extension knows a transaction is technically possible. + +A "payment manager" acts as the container for these sessions for a given browser tab. It maintains a list of all payment sessions (monetization links) in the exact order they appear on the page. This order is critical because it dictates which wallet address receives priority when the extension decides where to send money. Even if a site only has one wallet address, the payment manager still handles the lifecycle of that single session. + +When a website contains ` + +#### What if you switched the browser tab? + +One of the most common questions we receive is how the extension handles inactive tabs or minimized windows. To ensure you only pay for content you are actually consuming, the timer pauses the moment you switch tabs or move to another application. When you return, the timer resumes from its exact leftover duration rather than resetting. This focus-based logic prevents your budget from draining on background pages and ensures that every cent of your "streaming" payment is tied directly to your active attention [^2]. + +### Handling ` + +### Create outgoing payment + +With the recipient identified (who to pay), the timing set (when to pay), and the payment bucket filled (how much to pay), the Open Payments API finally enters the picture to move the money. Up until this point, all the logic has lived locally within the extension; now, we communicate with the outside world to execute the actual transaction. + +```js +const outgoingPayment = await openPaymentsClient.outgoingPayment.create( + { + accessToken: access_token.value, // found in Part 1 of article series + url: sender.resourceServer + }, + { + incomingPayment: session.incomingPaymentId, // receiver: found in Part 2 of article series + walletAddress: sender.id, // your wallet + debitAmount: { + value: amount.toString(), // $0.15 means "15" when assetScale is 2 + assetCode: sender.assetCode, + assetScale: sender.assetScale + } + } +) + +// inform the website about the payment via `monetization` event +session.sendMonetizationEvent(outgoingPayment) +``` + +## `MonetizationEvent` + +Once a payment is successfully processed, the website wants to know about it - whether that's to unlock premium content, hide advertisements, or update a global tip counter in real time. This communication happens via the monetization event. + +### The "polyfill" content script + +To make this work in browsers, the extension injects a content script. This script runs directly within the context of the web page, allowing the site to access the global `MonetizationEvent` interface. It also modifies `HTMLLinkElement.relList.supports` so developers can programmatically check if the browser supports Web Monetization: + +```ts +if (document.createElement('link').relList.supports('monetization')) { + console.log('Web Monetization is supported!') +} +``` + +### Informing website of payment + +Because this script runs in the website's own environment, we have to be extremely careful. We cannot expose extension internals or sensitive user data, as the website could intercept them, or clever users could create fake monetization events to trick the website. To bridge the gap safely, we use a message-passing system. + +When the extension's background script completes a payment, it sends a message to the content script using a randomly generated event name known only to these two components. This prevents the web page from eavesdropping on internal communication. The content script then receives this private message and dispatches a public `MonetizationEvent` on the web page. + +Websites can listen for these events globally on the window or on specific link elements, as these events bubble up the DOM tree: + +```js +window.addEventListener('monetization', (event) => { + console.assert(event instanceof MonetizationEvent) + + console.assert(event.target instanceof HTMLLinkElement) // the `` element that got paid + + const { currency, value } = event.amountSent + console.log(`Received ${currency} ${value}`) // -> Received USD 0.01 + + // Unlock premium content/features + showPremiumContent() +}) +``` + +## Epilogue + +I hope this three-part series has given you a clear map of the full Open Payments and Web Monetization flow. My goal was to provide enough technical depth that you feel confident building your own implementations - whether you're working on a [browser extension](https://github.com/interledger/web-monetization-extension), a server-side integration, or an entirely different platform outside the browser. + +The native browser implementations currently on the horizon will mostly follow a similar logic, though different teams will undoubtedly experiment with new approaches to find what works best for supporters and publishers. The architecture we've discussed in these articles is a reflection of the lessons we learned while building the extension, and we are always open to suggestions on how to refine it. By understanding these foundations, you're [ready to help](https://github.com/WICG/webmonetization) shape the future of how value moves across the web. + +[^1]: It is important to note that your actual spending can fluctuate. Because the extension triggers an initial payment the moment a page loads, visiting many different sites in a short burst might cause you to spend more than your average hourly rate within that specific window. If this activity exhausts your pre-allocated budget, the extension will stop all payments until you either top up your balance or the budget period resets. + +[^2]: It is worth noting that "focus" simply means the tab is the one currently in view. As long as the tab is active, the timer keeps running even if you aren't actively scrolling or clicking - after all, you might just be deeply absorbed in a long paragraph. We don't consider a user inactive based on movement yet; if the page is open and visible, the monetization continues.