+--------+ +---------+ +---------+
| | | |<------------| Remote |
| Client |------------>| Gateway | | Gateway |
| | | |------------>| |
+--------+ +---------+ +---------+
The Gateway Services API is a set of REST end-points implemented by gateways to provide common Ripple functionality to both Ripple clients and other Gateways. Features include:
- Gateway Deposits
- Gateway Withdrawals
- Inbound Bridge Payments
- Outbound Bridge Payments
- Multi-Bridge Remittance Payments
- Identifier Resolution (lookup)
- KYC Registration
- Sender and Receiver KYC Gating
- Opaque KYC Attestation
The Gateway Services API replaces the Federation name lookup and Bridge quote API calls. It unites those features with the functionality proposed for Manifests & Profiles. It supports Stefan's new road-map, by utilizing:
- Webfinger, for user and account info lookup (see: https://ripple.com/wiki/Identifiers#Interacting_with_identifiers)
- Host-meta, for gateway info lookup
- OpenID Connect for authentication
- OAuth2 Web Tokens for authorization
- Self-issued KYC claims for registration
- Gateway signed KYC opaque claims for attestation
The Gateway Services API extends the gatewayd data model. The core functionality can be implemented with only four API calls.
GET bridge_payment/quotes (returns transaction templates)
POST bridge_payment (returns gateway_tx_id)
GET bridge_payment/{gateway_tx_id} (transaction status)
POST bridge_payment/{gateway_tx_id}/cancel (cancel transaction)
A bridge payment is any payment between a Ripple account and an External account. Generally a "bridge payment" moves funds from one entity to another. "Deposits" and "withdrawals" move funds between accounts belonging to the same entity. This makes bridge payments a superset of deposits/withdrawals and allows the latter to be implemented using the former's routines.
Bridge payments are implemented analogously to REST payments:
- GET a set of possible "payment template" transactions
- CHOOSE the best payment to send
- SET any missing required properties, including KYC info
- POST the payment request to the gateway
- SEND a Ripple or External payment, including the ripple_invoice_id
- CHECK payment status to assure successful completion
Send money from an independent Ripple wallet to a "gated" Fidor bank account.
Fidor doesn't want its user's transactions indentifiable in the public ledger. As such it cannot use static destination tags to implement hosted wallets. Fidor also needs to be able to "gate"/refuse incoming payments from non-KYCed senders. Fidor implements this using bridge-payments.
Alice (a Fidor.ru known Ripple user) sending to Bob (a Fidor.de bank account holder)
Like the REST API's GET payment/paths, this call returns a list of bridge_payments that all meet the payment requirements specified in the URL parameters.
https://{gateway domain}/gateway/v1/{sender}/bridge_payment/quotes/{receiver}/{amount}
https://fidor.de/gateway/v1/[email protected]/bridge_payment/quotes/[email protected]/100+EUR
{
"bridge_payments": [
{
// Gateway Transaction Section
"gateway_tx_id": "9876",
"gateway_tx_type": "out",
"gateway_tx_state": "quote",
"gateway_tx_message": "Inactive, must be posted.",
// Acceptance Criterian
"destination_account": "ripple:r12345",
"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
"ripple_invoice_id": "8765",
"expiration": "1311280970",
// URL Specified by End-user
"url_sender": "[email protected]",
"url_receiver": "[email protected]",
"url_receiver_amount": {"amount":"100", "currency":"EUR"},
// Parties, KYC and Contract Terms
"sender": "acct:[email protected]",
"sender_account": "acct:ripple:[email protected]",
"outbound_bridge": "https://fidor.de",
"receiver": "acct:[email protected]",
"receiver_account": "acct:bank:[email protected]",
"receiver_amount": {"amount":"100", "currency":"EUR"},
}]
}
In this example there is only one payment option. That will be the case much of the time. However, in remittance situations their are often multiple possible sending or receiving agents. Each of these may have different terms and fees. As such, each is returned as a different possible transaction. The client should display the list to the end-user in order to choose the most appropriate option.
In this example there are no additional required claims. The gateway has been able to resolve all necessary KYC information based on the identifiers supplied in the URL.
In this example, to activate the quote the response is posted back as-is. Posting back the whole transaction allows the gateway to be stateless up to this point. This avoids storing quote resources that will never be activated. It also allows the client to implement a "GatewayPayment" class and serialize/deserialize it to JSON the same way every time.
https://{gateway domain}/gateway/v1/{sender}/bridge_payment
https://fidor.de/gateway/v1/[email protected]/bridge_payment
{
// Gateway Transaction Section
"gateway_tx_id": "9876",
"gateway_tx_type": "out",
"gateway_tx_state": "quote",
"gateway_tx_message": "Inactive, must be posted.",
// Acceptance Criterian
"destination_account": "ripple:r12345",
"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
"ripple_invoice_id": "8765",
"expiration": "1311280970",
// URL Specified by End-user
"url_sender": "[email protected]",
"url_receiver": "[email protected]",
"url_receiver_amount": {"amount":"100", "currency":"EUR"},
// Parties, KYC and Contract Terms
"sender": "acct:[email protected]",
"sender_account": "acct:ripple:[email protected]",
"outbound_bridge": "https://fidor.de",
"receiver": "acct:[email protected]",
"receiver_account": "acct:bank:[email protected]",
"receiver_amount": {"amount":"100", "currency":"EUR"},
}
{
// Gateway Transaction Section
"gateway_tx_id": "9876",
"gateway_tx_type": "out",
"gateway_tx_state": "pending",
"gateway_tx_message": "Active, awaiting Ripple payment.",
// Acceptance Criterian
"destination_account": "ripple:r12345",
"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
"ripple_invoice_id": "8765",
"expiration": "1311280970",
// URL Specified by End-user
"url_sender": "[email protected]",
"url_receiver": "[email protected]",
"url_receiver_amount": {"amount":"100", "currency":"EUR"},
// Parties, KYC and Contract Terms
"sender": "acct:[email protected]",
"sender_account": "acct:ripple:[email protected]",
"outbound_bridge": "https://fidor.de",
"receiver": "acct:[email protected]",
"receiver_account": "acct:bank:[email protected]",
"receiver_amount": {"amount":"100", "currency":"EUR"},
}
A successful post changes the gateway_transaction's state to "pending".
At this point the gateway transaction is in the "pending" state awaiting to accept the specified incoming payment. Before the expiration date, the user should send a ripple payment to:
"destination_account": "ripple:r12345",
"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
"ripple_invoice_id": "8765",
The incoming Ripple payment will identify the gateway transaction by setting the Ripple payment's invoice_id field.
Since this is an asychronous process, the client must check the transaction state and report the progress to the end-user.
https://{gateway domain}/gateway/v1/{sender}/bridge_payment/{gateway_tx_id}
https://fidor.de/gateway/v1/[email protected]/bridge_payment/9876
{
// Gateway Transaction Section
"gateway_tx_id": "9876",
"gateway_tx_type": "out",
"gateway_tx_state": "delivered",
"gateway_tx_message": "Complete, payment has arrived in the destination account.",
// Acceptance Criterian
"destination_account": "ripple:r12345",
"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
"ripple_invoice_id": "8765",
"expiration": "1311280970",
// URL Specified by End-user
"url_sender": "[email protected]",
"url_receiver": "[email protected]",
"url_receiver_amount": {"amount":"100", "currency":"EUR"},
// Parties, KYC and Contract Terms
"sender": "acct:[email protected]",
"sender_account": "acct:ripple:[email protected]",
"outbound_bridge": "https://fidor.de",
"receiver": "acct:[email protected]",
"receiver_account": "acct:bank:[email protected]",
"receiver_amount": {"amount":"100", "currency":"EUR"},
}
Sometimes the process will not go as smoothy as above. For example, perhaps the bridge doesn't know the sender.
Alice (unknown to Fidor) sending to Bob (a Fidor.de bank account holder)
If one or both of the parties is unknown, the gateway will include a list of the required KYC information to be submitted with the request. Notice the following properties:
"sender": "",
"sender_claims_required": ["given_name", "family_name", "address.formatted", "email", "phone_number"],
"sender_claims_jwt": [],
Here the bridge was not able to resolve the sender's identity internally or by using webfinger.
The bridge will not process the payment without the sender supplying additional information about the sender. This information is submitted as OpenID format claims. These claims can be provided and signed a third-party or by the subject him/herself.
These claims can also include "attestations" from the issuer about the level of verification that was done on the claims. That allows the receiver to decide if they are willing to trust the issuers verification in leu of doing their own extended verification.
{
"sub": "acct:[email protected]",
"iss": "acct:snapswap.com",
"iat": "1311280970",
"given_name":"Bob",
"family_name":"White",
"address.formatted":"candyland",
"email":"[email protected]",
"ripple_address": "ripple:r45678"
"email_verified":"true",
"phone_number_verified":"true",
"id_verified":"true",
"id_face_matched":"true",
}
Each set of claims is encapulated into a web token signed by the claim issuer.
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
In this example, the end-user "alice" is submitting her own identity claims. She is also including claims and attestations provide by her Ripple gateway.
These claims are submitted as an array of signed web token claims.
"sender_claims_jwt": [
// end-user issued claims
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk,
// gateway issued claims
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk,
],
If the client fails to submit all the required fields, the response code is 400 Bad Request and the result will contain the gateway-payment transaction updated to list the still missing fields.
"sender": "acct:[email protected]",
"sender_claims_required": ["phone_number"],
"sender_claims_jwt": [],
A gateway deposit is really just an outbound bridge call in which the sending external account and receiving ripple account owners are the same person. A gateway withdrawal is an equivalent outbound bridge call.
Normally, the gateway establishes a standing "policy" that ties the two accounts together any fees. A user's policy is used as the template for each new gateway_transaction. Because a user is KYCed when establishing their gateway policy, no additional KYC is needed for deposits or withdrawals.
- GET gateway_deposit/quotes
- POST gateway_deposit
- GET gateway_deposit/{gateway_tx_id} (transaction status)
- POST gateway_deposit/{gateway_tx_id}/cancel (cancel transaction)
- GET gateway_withdrawal/quotes
- POST gateway_withdrawal
- GET gateway_withdrawal/{gateway_tx_id} (transaction status)
- POST gateway_withdrawal/{gateway_tx_id}/cancel (cancel transaction)
Alice is logged in to her Ripple client and she wants to send 100 EUR to Bob. Bob is either 1) a Ripple account holder, 2) a Bank account holder, or 3) unbanked. In all three cases Alice needs an identifer for Bob to enter into her clients's "send to:" field.
She can enter Bob's:
- Ripple name, federation name, or Ripple address.
- Federation name, or federation style bridge address
- Domain name, of the remittance location/network where Bob can pickup the money
The client then needs to resolve the identifiers in order to determine the identities of the account owners and to query the proper bridge.
Given Alice is logged in as ~alice (ripple:r56789), the client will:
-
Resolve ~alice to r56789 using httpid.ripple.com
-
Query r56789's ledger account_root for its domain property "host.com"
-
Query the webfinger service @ "domain", e.g. https://host.com/.well-known/webfinger?resource=ripple:r56789
-
Read Alice's preferred identifier from the webfinger JRD "properties" section.
-
If no preferred_username field, hash each aliases and compare to account_root's email_hash
"properties": { "preferred_username": "[email protected]", },
Resolve [email protected]
Given Alice entered "[email protected]", the client will:
-
Query the webfinger service, e.g. https://fidor.de/.well-known/webfinger?resource=acct%3Abob%40fidor.de
-
Read Bob's preferred identifier from the webfinger JRD "properties" section.
-
Read Bob's public claims from JRD "properties".
-
Read the gateway api location from the JRD "links".
"links": [{ "rel": "https://ripple.com/gateway_services", "href": "https://api.fidor.de/gateway_services", }]
https://api.fidor.de/gateway_services/v1/[email protected]/bridge_payment/quotes/[email protected]/100+EUR
Resolve [email protected]
Resolve [email protected]
-
Query the webfinger service, e.g. https://fidor.de/.well-known/webfinger?resource=acct%3Abob%40fidor.de
-
Determine the account owner".
"properties": { "owner": "acct:123456890", },
-
Read Bob's private claims from JRD "properties".
- KYC Registration
- Sender and Receiver KYC Gating
- Opaque KYC Attestation
Quoted Bridge Payments Gated Hosted Wallets(pre_tx gating by tag or inv_id) Gated Withdrawals (pre_tx gating by tag or inv_id)
Unquoted Bridge Payments Hosted Wallets (post_tx gating by tag or sending address) Direct withdrawals (post_tx gating by tag or sending address)
http://openid.net/specs/openid-connect-core-1_0.html#rnc
http://openid.net/specs/openid-connect-core-1_0.html#Terminology
The Ripple Service Layer implements a distributed identity resolution. Rather then maintaing different user/pass combinations at each gateway, or relying on a centralized server, gateways relys on Ripple clients implementing a "self-issuing" OpenID identity provider.
This allows each end-user to maintain their own user profile and control submission of their own personally identifying (KYC) information. It also gives each end-user a single sign-on process that works with both with their Ripple client and any Ripple gateway.
The OpenID Connect "self-issued" protocol, in abstract, follows the following steps.
The RP (Client) sends a request to the OpenID Provider (OP). The OP authenticates the End-User and obtains authorization. The OP responds with an ID Token containing Claims about the End-User. These steps are illustrated in the following diagram:
+--------+ +--------+
| | | |
| |---------(1) AuthN Request-------->| |
| | | |
| | +--------+ | |
| | | | | |
| | | End- |<--(2) AuthN & AuthZ-->| |
| | | User | | |
| RP | | | | OP |
| | +--------+ | |
| | | |
| |<--------(3) AuthN Response--------| |
| | | |
+--------+ +--------+
http://openid.net/specs/openid-connect-core-1_0.html#Overview