# Web Monetization ## Use Case - **Webmonize** is a web monetization provider that wishes to connect to Alice's Open Payments account - **Fynbos** is Alice's account provider - **Uphold** is Bob's account provider - **Alice** is an account holder at Fynbos and a user at Webmonize - **Bob** is an account holder at Uphold - Bob has a Payment Pointer from Uphold that he has embedded on his website. - Alice is visiting Bob's website and wishes to send Bob web monetization payments. - Alice has the Webmonize browser extension installed ### Out of Scope - How does Fynbos verify Webmonize's identity as a client? - How does Alice verify that the account she is sending web monetization payments to belongs to Bob? ### Step 1: Alice connects her Fynbos account to Webmonize Alice wishes to give Webmonize the ability to send recurring payments on her behalf from her Fynbos account. Alice provides Webmonize with her Payment Pointer: `https://fynbos.me/alice` Webmonize queries the Payment Pointer to get the grant request endpoint URL. ```http GET /alice HTTP/1.1 Host: fynbos.me Accept: application/op-account-v1+json ``` Fynbos responds: ```http HTTP/1.1 200 Success Content-Type: application/op-account-v1+json { "id": "https://fynbos.me/alice", "publicName": "Alice" "assetCode": "USD", "assetScale": 2 "authServer": "https://fynbos.dev/auth" } ``` - `id` is the unique URL identifying this account. It may be different to the URL that was used in the original client request if the account has multiple aliases or the request weas redirected from another URL. - `publicName` is a name that Alice chose to display puclicly for her account. Since this endpoint can be accessed anonymously this value is set by the account holder and can be any value. It is not used for verfication. - `assetCode` and `assetScale` indicate the currency of the account. - `authServer` is the endpoint of the AS where grants can be obtained to perform actions on this account. Webmonize makes a Grant Request to Fynbos via the AS endpoint at `https://fynbos.dev/auth`: ```http POST /auth HTTP/1.1 Host: fynbos.dev Content-Type: application/json Signature-Input: sig1=... Signature: sig1=... Digest: sha256=... { "access_token": { "access": [ { "type": "outgoing-payment", "actions": [ "create", "authorize" ], "locations": [ "https://fynbos.me/alice" ] } ] }, "client": { "display": { "name": "Webmonize", "uri": "https://webmonize.com" }, "key": { "proof": "httpsig", "jwk": { "kty": "RSA", "e": "AQAB", "kid": "xyz-1", "alg": "RS256", "n": "kOB5rR4Jv0GMeL...." } } }, "interact": { "start": [ "redirect" ], "finish": { "method": "redirect", "uri": "https://webmonize.com/return/876FGRD8VC", "nonce": "LKLTI25DK82FX4T4QFZC" } } } ``` - `access_token` details the requested grant. Webmonize is requesting the ability to create and authorise outgoing payments from Alice's account. - `client` is where Webmonize describes itself and provides the key that it uses to identify itself. Since Webmonize is presumably already known to Fynbos this request will be authenticated (signed with that key). - `interact` details the mechanisms available for Fynbos to interact with Alice via Webmonize if required. Fynbos sends back an instruction to Webmonize to redirect Alice to a Fynbos web page to authenticate herself and consent to the grant request. ```http HTTP/1.1 201 Created Content-Type: application/json { "interact": { "redirect": "https://fynbos.dev/auth/4CF492MLVMSW9MKMXKHQ", "finish": "MBDOFXG4Y5CVJCX821LH" }, "continue": { "access_token": { "value": "80UPRY5NM33OMUKMKSKU" }, "uri": "https://fynbos.dev/continue/4CF492MLVMSW9MKMXKHQ" } } ``` - `interact` is the details of the redirect instruction with a unique key to secure the callback - `continue` defines the callback URL and an access token to use when making the callback Webmonize directs Alice to `https://fynbos.dev/auth/4CF492MLVMSW9MKMXKHQ` which is a web page hosted by Fynbos. Fynbos authenticates Alice and then prompts Alice to consent to giving Webmonize access to her account. Fynbos allows Alice to specify a limit on what Webmonize will be able to do. Example: > Webmonize will be able to make payments from your account up to a total of $____ per ____. Alice specifies $10 per month and Fynbos creates and records the new grant issued to Webmonize in its own system. Fynbos redirects Alice to `https://webmonize.com/return/876FGRD8VC?hash=p28jsq0Y2KK3WS__a42tavNC64ldGTBroywsWxT4md_jZQ1R2HZT8BOWYHcLmObM7XHPAdJzTZMtKBsaraJ64A &interact_ref=4IFWWIKYBC2PQ6U56NL1` - The URL was provided by Webmonize in the grant request - `interact_ref` is a random value generated by Fynbos - `hash` was calculated by Fynbos based on: - the `nonce` Coil provided in the original request (`LKLTI25DK82FX4T4QFZC`), - the `nonce` Fynbos provided in the response (`MBDOFXG4Y5CVJCX821LH`) - the `interact_ref`, and - the URL of the grant request endpoint `https://fynbos.dev/auth`. Webmonize validates the hash and then makes a continue request to Fynbos at `https://fynbos.dev/continue/4CF492MLVMSW9MKMXKHQ` using the access token `80UPRY5NM33OMUKMKSKU`. ```http POST /continue/4CF492MLVMSW9MKMXKHQ HTTP/1.1 Authorization: GNAP 80UPRY5NM33OMUKMKSKU Host: fynbos.dev Signature-Input: sig1=... Signature: sig1=... ``` Fynbos responds to Webmonize with the following response: ```http HTTP/1.1 200 Success Content-Type: application/json { "access_token": { "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0", "manage": "https://fynbos.dev/auth/token/PRY5NM33OM4TB8N6BW7", "expires_in": 762534, "access": [ { "type": "outgoing-payment", "actions": [ "create", "authorize", ], "locations": [ "https://fynbos.me/alice" ], "limits": { "startAt": "2022-02-01T00:00:00.000Z", "expiresAt": "2023-01-31T23:59:59.999Z", "interval": "P1M", "sendAmount": { "amount": 1000, "assetCode": "USD", "assetScale": 2 } } }] }, "continue": { "access_token": { "value": "YT98RY5NM33OMUKMK65U" }, "uri": "https://fynbos.dev/auth/continue" } } ``` Webmonize now has a grant to create and authorize outgoing payments from Alice's account up to a value of $10 per month. ### Step 2: Alice sends web monetization payments to Bob through the Webmonize extension Alice is on Bob's website and has activated web monetization on the Webmonize extension. The extension uses the grant to obtain an access token. The extension passes the following config details to the user agent: | Property | Description | | -- | -- | paymentPointer | https://fynbos.me/alice | amount | 200 | assetCode | USD | assetScale | 4 | interval | PT1M | token | OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 where | Property | Type | Description | | -- | -- | -- | paymentPointer | URL | The URL identifying the sender's account | amount | uint64 | The amount that must be paid into the receiver’s account. | assetCode | string | The asset code of the amount. This SHOULD be an ISO4217 currency code. | assetScale | uint32 | The scale of the amount. | interval | string | The payment recurrence. This SHOULD be an ISO8601 duration. | token | | The GNAP access token. `amount`, `assetCode`, `assetScale`, and `interval` could be set by Webmonize or Webmonize allows Alice to configure that in her user account or even in the extension. Alice's user agent has parsed Bob's Payment Pointer from the page: `https://uphold.com/bob` Alice's user agent creates the outgoing payment from Alice's account by posting a new outgoing payment resource to her Payment Pointer: ```http POST /alice HTTP/1.1 Host: fynbos.me Content-Type: application/op-outgoing-payment-v1+json Accept: application/op-outgoing-payment-v1+json Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client" Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==: Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 { "state": "authorized", "receivingAccount": "https://uphold.com/bob", "sendAmount": { "amount": 200, "assetCode": "USD", "assetScale": 4 } } ``` - `state` is already set to authorized as Webmonize has permission to do this and isn't going to wait for a quote from Fynbos before authorizing. - `receivingAccount` is Bob's Payment Pointer. - `sendAmount` is the amount to send from Alice's account. - `description` is used to provide a description for Bob on the incoming payment. Within Fynbos the resource server (Rafiki) checks the grant associated with the provided token by making an introspection request to the authorisation server (AS) and gets the following response body: ```json { "active": true, "grant": "PRY5NM33OM4TB8N6BW7", "access": [ { "type": "outgoing-payment", "actions": [ "create" "authorize" ], "locations": [ "https://fynbos.me/alice/" ], "startAt": "2022-02-03T18:25:43.511Z", "expiresAt": "2023-02-03T18:25:43.511Z", "limits": { "startAt": "2022-02-03T18:25:43.511Z", "expiresAt": "2023-02-03T18:25:43.511Z", "interval": "P1M", "sendAmount": { "amount": 1000, "assetCode": "USD", "assetScale": 2 } } } ], "key": { "proof": "httpsig", "jwk": { "kty": "RSA", "e": "AQAB", "kid": "xyz-1", "alg": "RS256", "n": "kOB5rR4Jv0GMeL...." } }, } ``` - `active` indicates the token is valid (hasn't expired or been revoked.) - `access` describes the permissions granted by this token - `key` defines the client key that the grant is bound to and that should be used to sign the request (i.e. the token is not a bearer token) The RS tracks balances for each time interval as required for every `grant`. Even though the AS confirms that this access token is valid, the RS may decline the client's request if the amount that it wants to send exceeds the limit per interval. In this case, Fynbos has no record of any transactions using this grant in the current month so it creates a new balance to track payments for this month. The balance of `PRY5NM33OM4TB8N6BW7_FEB_22` is $0 and will go up to $0.02 if this payment completes. Fynbos is happy that the user agent is authorised via Webmonize to create the payment so it does so and returns the following response: ```http HTTP/1.1 201 Created Content-Type: application/op-outgoing-payment-v1+json { "id": "https://fynbos.me/alice/fi7td6dito8yf6t" "accountId": "https://fynbos.me/alice/", "state": "authorized", "sendAmount": { "amount": 200, "assetCode": "USD", "assetScale": 4 } "receipts: [] } ``` Fynbos now attempts to create an incoming payment at Bob's payment pointer to accept the payment. > **TODO** This request should be authorised. It is likely that we'll want all ASPSPs to have keys that are trusted by all other ASPSPs. This will not be for anything but creating and querying low-risk resources such as incoming payments and account holders. ```http POST /bob HTTP/1.1 Host: uphold.com Content-Type: application/op-incoming-payment-v1+json Accept: application/op-incoming-payment-v1+json { "receiptsEnabled": true } ``` - `receiptsEnabled` requests that the receiver return receipts. Uphold creates the incoming payment and returns the Interledger payment details. Since Fynbos requested receipts, Uphold has generated a nonce and secret for signing the receipts. ```http HTTP/1.1 201 Created Content-Type: application/op-incoming-payment-v1+json { "id": "https://uphold.com/bob/87tfi7td6dito8yf", "accountId": "https://uphold.com/bob", "state": "pending", "ilpAddress": "g.uphold.Cty6C+YB5X9FhSOUPCL", "sharedSecret": "6jR5iNIVRvqeasJeCty6C+YB5X9FhSOUPCL/5nha5Vs=", "receiptNonce": "Cty6C+YB5X9FhSOUPCL" } ``` - `id` the URL identifier of the new incoming payment - `accountId` the URL identifier of the account getting paid - `state` the state of the payment is pending so it is still able to receive payments - `ilpAddress` the ILP address to send to - `sharedSecret` the shared secret to use for the STREAM connection - `receiptNonce` indicates that the STREAM connection will return receipts with this nonce. Fynbos opens a STREAM connection and starts sending until it has sent $0.02 from Alice's account. It stores the latest receipt for each STREAM stream as they are received. At any time, the user agent may poll the outgoing-payment for the latest state: ```http GET /alice/fi7td6dito8yf6t HTTP/1.1 Host: fynbos.me Accept: application/outgoing-payment-v1+json ``` The response includes the STREAM receipts that have been received by Fynbos. A response that is received while the payment is in progress might look like this: ```http HTTP/1.1 200 Success Content-Type: application/op-outgoing-payment-v1+json Cache-Control: no-cache { "id": "https://fynbos.me/alice/fi7td6dito8yf6t", "accountId": "https://fynbos.me/alice/", "state": "authorized", "receivingPayment": "https://uphold.com/bob/87tfi7td6dito8yf", "sendAmount": { "amount": 200, "assetCode": "USD", "assetScale": 4 }, "receipts": [ { "nonce": "VR66CYB5X9FhSOUPCL", "stream": 0, "total": 73, "hmac": "9B025X9FhSO3UPCCty6C+YB5X9FhSOUP5CL" } ] } ``` The user agent dispatches the `monetization` event including the `receivingPayment` URL (the incoming payment) and the receipts. Bob's web page is able to query the incoming payment at Uphold and get the receipt secret. The request from Bob must be authenticated as only Bob should get the receipt secret in the response. ```http GET /bob/87tfi7td6dito8yf HTTP/1.1 Host: uphold.com Accept: application/op-incoming-payment-v1+json Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="bob" Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==: Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 ``` The response includes the full state of the incoming payment and because Bob has the permission to see it, it also includes the receipt secret. ```http HTTP/1.1 200 Success Content-Type: application/op-incoming-payment-v1+json { "id": "https://uphold.com/bob/87tfi7td6dito8yf", "accountId": "https://uphold.com/bob", "state": "processing", "receivedAmount": { "amount": 12, "assetCode": "USD", "assetScale": 4 }, "receiptSecret": "Aws5B78B8H9FhTRSPAB", "receipts": [ { "nonce": "Cty6C+YB5X9FhSOUPCL", "stream": 0, "total": 12, "hmac": "6CsYB5X9FhSO3UPCCty7FB5X9FhSOUP5CL" } ] } ``` Bob's web page can verify the receipts. Alternatively, the process of generating the secret from the nonce is known to Bob's web page and it can re-generate the secret itself and verify the receipts. ### Step 3: Alice continues to send web monetization payments to Bob After a minute has passed, the user agent sends a PATCH request to the outgoing payment URL to update the amount. ```http PATCH /alice/fi7td6dito8yf6t HTTP/1.1 Host: fynbos.me Content-Type: application/op-outgoing-payment-v1+json Accept: application/op-outgoing-payment-v1+json Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client" Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==: Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 { "state": "authorized", "sendAmount": { "amount": 400, "assetCode": "USD", "assetScale": 4 } } ``` Within Fynbos the resource server (Rafiki) checks the grant associated with the provided token by making an introspection request to the authorisation server (AS). The AS confirms that this access token is valid. The RS checks the balance of the grant, `PRY5NM33OM4TB8N6BW7_FEB_22`, which is $0.02 and will go up to $0.04 if this payment completes. Fynbos is happy that Webmonize is authorised to patch the payment so it does so and returns the following response: ```http HTTP/1.1 200 Success Content-Type: application/op-outgoing-payment-v1+json { "id": "https://fynbos.me/alice/fi7td6dito8yf6t" "accountId": "https://fynbos.me/alice/", "state": "authorized", "receivingPayment": "https://uphold.com/bob/87tfi7td6dito8yf", "sendAmount": { "amount": 400, "assetCode": "USD", "assetScale": 4 } "receipts: [ { "nonce": "Cty6C+YB5X9FhSOUPCL", "stream": 0, "total": 198, "hmac": "6C+YB5X9FhSO3UPCCty6C+YB5X9FhSOUP5CL" } ] } ``` Fynbos opens a STREAM connection and starts sending until it has sent $0.02 from Alice's account. It stores the latest receipt for each STREAM stream as they are received. At any time, the user agent may poll the outgoing-payment for the latest state. The response includes the STREAM receipts that have been received by Fynbos. The user agent dispatches the `monetization` event including the `receivingPayment` URL (the incoming payment) and the receipts. Bob's web page can verify the receipts as detailed above. ### Step 4: Alice leaves Bob's web page Once Alice leaves Bob's web page, the user agent sends a PATCH request to the outgoing payment to explicitly complete it: ```http PATCH /alice/fi7td6dito8yf6t HTTP/1.1 Host: fynbos.me Content-Type: application/op-outgoing-payment-v1+json Accept: application/op-outgoing-payment-v1+json Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client" Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==: Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 { "state": "completed", } ``` Consequently, Fynbos marks the corresponding incoming payment complete: ```http PUT /bob/87tfi7td6dito8yf HTTP/1.1 Host: uphold.com Content-Type: application/op-incoming-payment-v1+json Accept: application/op-incoming-payment-v1+json { "status": "complete" } ``` Uphold responds: ```http HTTP/1.1 200 Success Content-Type: application/op-incoming-payment-v1+json { "id": "https://uphold.com/bob/87tfi7td6dito8yf", "accountId": "https://uphold.com/bob", "state": "completed", "receivedAmount": { "amount": 396, "assetCode": "USD", "assetScale": 4 }, "receipts": [ { "nonce": "Cty6C+YB5X9FhSOUPCL", "stream": 0, "total": 396, "hmac": "hSO3UPC6C+YB5X9FCty6C+YB5X9FhSOUP5CL" } ] } ``` - `status` indicated that the incoming payment is complete and the STREAM connection won't accept any more incoming packets. - `receivedAmount` indicates the amount received ($0.0396). - `receipts` is an array of the latest receipt for each stream on the underlying STREAM connection.