Introducing Pull Payments to the Interledger Protocol

Header image by Dragan Miljkovic on Unsplash

In its current design, the Interledger Protocol (ILP) only allows for push payments. Imagine BigCompany accepts ILP payments of any currency and Alice wants to utilize this to pay for CoolProduct. All she needs is an identifier for BigCompany's account—the payment pointer—maybe even a specific one for her customer account at BigCompany, and off the packets of value go. But what if CoolProduct is actually a CoolSubscription? Alice does not want to be bothered to send an equal amount to BigCompany's payment pointer every month in order to extend her subscription. She wants BigCompany to directly pull this amount from her account, just like using a credit card for recurring payments.

Recap: Push Payments

To understand the process of pull payments, let me first briefly recap how push payments work.

Alice runs the SPSP Client and BigCompany hosts the SPSP Server. In the simplest scenario, both are connected to the Interledger network through moneyd. BigCompany's SPSP Server exposes multiple HTTPS endpoints that are abstracted by payment pointers. Alice queries her corresponding customer payment pointer $bc.com/alice (1), which returns the ILP destination_account and shared_secret (2). Using these two parameters, Alice's SPSP Client opens a STREAM connection and sends value to BigCompany (3). BigCompany's SPSP server will update Alice's balance accordingly. Done.

If BigCompany were offering some CoolSubscription, Alice had to do this process every subscription interval to keep her plan. If she forgets it once, her subscription is cancelled and she has to go through the registration process again. This is way more tedious than just paying with credit card. If only there were a way to authorize BigCompany to pull the subscription fee every interval. Now there is!

Introducing Pull Payments

Pull payments are comprised of three stages:

1. the negotiation

2. the pull pointer creation

3. the actual pull

The negotiation phase

Before a pull can happen, the two counterparties—Alice and BigCompany—have to decide on the terms and conditions of the pull. Alice wants BigCompany to only pull the subscription fee, denoted in USD, every month for the next 12 months. BigCompany wants to have the opportunity to pull January's fee in February without losing out on these profits, i.e., BigCompany does not want the pull amount to be capped. If it already existed, a UI enabled version of this negotiation process would look like this:

Alice decides she really wants CoolSubscription, offered by BigCompany, so she taps on “Get” (1). BigCompany asks Alice to create a pull pointer, a special payment pointer including a token, having the sole purpose of paying for CoolSubscription (2). This pointer needs the following information:

- Amount: Amount, denoted in Asset Code, which can pulled by BigCompany each Interval.

- Asset Code: Asset Code corresponding to Amount, e.g USD.

- Asset Scale: The scale of Amount, e.g. an Amount of 1000 with a 

Asset Scale of 2 and an Asset Code of USD translates to 10.00 USD.

- Interval: ISO8601 duration, e.g. P0Y1M = 1 month, which describes how often BigCompany can pull Amount.

- Cycles: Number of times Interval is repeated.

- Cap: Defines whether any funds not pulled before the start of the next interval cycle is accumulated or expires.

- Start: (optional) ISO8601 UTC timestamp, representing the time at which BigCompany can pull for the first time.

Most of these fields are set by BigCompany and Alice cannot alter them. However, she should be able to change the Cycles field, indicating for how long she is subscribing to CoolSubscription. Granted, this negotiation phase is quite short. One could imagine a process where BigCompany first asks Alice what currency she wants to use, then how long she wants her subscription to last and based on these details, she receives a quote. If she doesn't like this quote, she changes currency and/or the number of cycles and BigCompany will offer her a new price. Both go back and forth until they are satisfied.

The pull pointer creation

Once Alice and BigCompany agree on the parameters of the subscription, Alice needs to create the according pull pointer. Let's imagine there was already a wallet provider that had a UI for pull pointer creation called The Wallet.

When Alice clicks “Create” in (3), she is redirected to her wallet, which asks her to double check the parameters of the pull pointer. She clicks “Create” and the pull pointer is generated (4). 

There are two ways to generate the pull pointer—online and offline. In the online version, The Wallet app sends a POST request to its SPSP Pull Server, an SPSP server that, instead of handling incoming payments, handles outgoing or pulled funds. The POST request includes all of the negotiated parameters, i.e.

$ http POST thewallet.com amount=1000 interval=P0Y1M cycles=12 cap=false assetCode=USD assetScale=2 Authorization:"Bearer test"

which it uses to create a database entry. It responds with the corresponding pull pointer:

HTTP/1.1 200 OK

Connection: keep-alive

Content-Length: 78

Content-Type: application/json; charset=utf-8

Date: Wed, 06 Mar 2019 00:42:30 GMT

Server: nginx/1.10.1

{

"pointer": "$thewallet.com/ew45kh94bs74qb67tn"

}

The endpoint is password protected such that only authorized requests are processed. Alice needs to be online to create this pull pointer because her The Wallet app needs to communicate with the SPSP Pull Server.

Excursion: Offline pull pointer generation

While this is surely the case Alice is online for online shopping, one can think of use cases where she would like to create a pull pointer while being offline.

Maybe Alice is at her favorite beach bar in rural Mexico that does not have free Wifi (yes, these places do still exist) but the bar offers a special deal on beer. If Alice provides a pull pointer, she only pays 0.12 MXN (0.006 USD) per barn-megaparsec. Luckily, The Wallet app comes with the possibility to create offline pull pointers. These are pull pointers that include a JSON Web Token (JWT), signed by a secret that is shared between Alice's instance of The Wallet app and the The Wallet's SPSP Pull Server. When the SPSP Pull Server receives a pull request for this pointer, which does not exist in the SPSP Pull Server's database yet, it verifies and parses the JWT. Afterwards, it creates an entry for the pull pointer in its database and processes the pull. Naturally, the pull pointer is much longer because it needs to accommodate all of the negotiated parameters within the JWT, e.g.

$thewallet.com/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbW91bnQiOiIxMDAwMCIsImludGVydmFsIjoiUDBZME0wRDFIIiwiY3ljbGVzIjoiMyIsImNhcCI6ImZhbHNlIiwiYXNzZXRDb2RlIjoiTVhOIiwiYXNzZXRTY2FsZSI6IjIiLCJpYXQiOjE1NTE4MTY1MTl9.PqR1ji0ZZpZXqrMSq2k5c27S5pr0CcLN71OdxAYlsvE

Let us return to the CoolSubscription example. Alice's The Wallet app has created a pull pointer (4) with which she returns to the checkout process on BigCompany's webpage. She submits the pull pointer (5) and voilà, Alice has successfully subscribed to CoolSubscription (6).

The pull

Before BigCompany responds with its happy message of completion, it will pull for the first time. What does that entail?

BigCompany's SPSP Client queries the pull pointer that Alice provides (1) and receives the endpoint's ILP destination_account and shared_secret (2). It uses those two to open a STREAM connection to the SPSP Server. Once connected, it uses STREAM's receiveTotal to receive the funds it needs to activate Alice's subscription, i.e. the 10.00 USD (3). On completion, the SPSP Client will close the STREAM connection. At that point, it would trigger a webhook that redirects Alice to the completion view.

A month later, BigCompany will attempt to pull again and if it is successful, it will renew Alice's subscription for another month. This continues until all the cycles have passed. However, if Alice really enjoys her CoolSubscription, she can update her pull pointer and increase the number of cycles. BigCompany may also increase the price of CoolSubscription, forcing Alice to update the amount within the pull pointer. If she fails to do so, her subscription will automatically end. Technically, the update is another POST request to The Wallet's SPSP Server:

http POST thewallet.com/update/ew45kh94bs74qb67tn cycles=24 Authorization:"Bearer test"

HTTP/1.1 200 OK

Connection: keep-alive

Content-Length: 225

Content-Type: application/json; charset=utf-8

Date: Wed, 04 Mar 2020 22:57:28 GMT

Server: nginx/1.10.1

{

"amount": "1000",

"assetCode": "USD",

"assetScale": "2",

"balanceAvailable": "0",

"balanceInterval": "1000",

"balanceTotal": "12000",

"cap": false,

"cycleCurrent": 12,

"cycles": "24",

"interval": "P0Y1M",

"start": "2019-03-06T00:42:30.893Z"

}

If Alice hates CoolSubscription, she can also cancel it at any point by updating the pull pointer and reducing the number of cycles.

Give me more details!

Obviously, this post mostly tells the story but does not go into much technical detail. Here are links to bridge the gap:

- SPSP spec

- SPSP pull payment spec

- SPSP Pull Server implementation

- SPSP protocol library including pull payment support: Pull Request

- SPSP CLI including pull payment support: Pull Request

Thanks to Brandon Wilson and Ben Sharafian for your helpful input when writing this article.

Other use cases

Continue reading with a Coil membership.