# Proposal: Data Callback Capability (Wallet Webhooks)
## Abstract
Defines a new EIP-5792 capability that allows developers to request user information as part of a transaction request. Information types are expected to be well-defined and agreed upon in separate ERCs (except the ones defined here).
In addition to requesting information, developers can provide callback URLs that validate the supplied information, where "validation" can mean both:
- Ensuring the provided data is in the correct format.
- The provided data meets any other constraints the developer may set based on their use case.
## Motivation
Developers need a way to request and receive arbitrary pieces of information from their users. While the data types defined in this proposal are focused on ecommerce (contact / shipping information), this specification leaves room for developers and wallets to propose any other data types they might find useful.
## Specification
One new EIP-5792 capability is defined. We also define three request types to be used with the new capability.
### `dataCallback` Capability
```typescript!
type DataRequest = {
type: string
required: boolean
}
type DataCallbackParams = {
requests: DataRequest[]
collectionURL: string
validationURL?: string
updateURL?: string
}
```
### Implementation and Usage
#### `collectionURL`
The simplest way to use the data callback capability is to specify your requested data with just a `collectionURL`. This will prompt the wallet to `POST` the requested data along with the EIP-5792 `callsId`, a `statusURL` where an application can submit `wallet_getCallsStatus` requests to, and the `walletAddress` that submitted the calls onchain to the specified URL.
```mermaid
sequenceDiagram
participant app.com
participant Wallet
participant Bundler / Node
participant collection.app.com
app.com->>Wallet: wallet_sendCalls({...dataCallback})
Wallet->>Bundler / Node: Submit calls
Bundler / Node->>Wallet: Hash
Wallet->>collection.app.com: POST {callsId, statusURL, dataCallback, walletAddress}
collection.app.com->>Wallet: OK
Wallet->>app.com: callsId
```
```mermaid
sequenceDiagram
participant app.com
participant Wallet
participant Bundler / Node
app.com->>Wallet: wallet_sendCalls({...dataCallback})
Wallet->>Bundler / Node: Submit calls
Bundler / Node->>Wallet: Hash
Wallet->>app.com: {callsId, dataCallback}
```
##### App
To request information from the wallet, include the `dataCallback` capability in an EIP-5792 request:
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: '...',
required: true
}
{
type: '...',
required: false
}
],
collectionURL: 'https://...',
}
}
})
```
Assuming it is capable of providing the requested information, the wallet will submit a `POST` request to the supplied `collectionURL` according to the specification in the following section. Upon successful collection, the app's `collectionURL` MUST respond with a `200` HTTP status code.
##### Wallet
Upon receiving a `wallet_sendCalls` request with a `dataCallback` capability, the wallet MUST reject the request if the `dataCallback` capability requests information the wallet is unable to supply, unless the request explicitly specifies that the information is not required (determined by checking `request.required`).
The wallet may choose to collect the requested information however it wants.
After the requested information is collected from the user, the wallet MUST submit the `calls` specified according to EIP-5792 and then submit a `POST` request with the requested information to the specified `collectionURL` as follows:
###### Specification
```typescript!
type DataCallbackCollectionParams = {
callsId: string // EIP-5792 callsId
statusURL: string // A URL the app can submit `wallet_getCallsStatus` requests to
dataCallback: Record<string, any> // Keys are the requested data types, and values are the corresponding values supplied by the wallet
walletAddress: `0x${string}` // The address of the account that submitted the calls onchain
}
```
###### Example
```typescript!
fetch(capabilities.dataCallback.collectionURL, {
method: 'POST',
body: JSON.stringify({
callsId: '...',
statusURL: 'server.wallet.com',
dataCallback: {
email: 'foo@bar.com',
phoneNumber: {
country: 'US',
number: '123-456-7890'
}
},
walletAddress: '0x...'
})
})
```
#### `validationURL`
If an application wants to check the provided information before allowing the user to submit the transaction onchain, it can supply, in addition to a `collectionURL`, a `validationURL`. This proposal already notes that wallets MUST supply requested information according to data types defined and agreed upon in ERCs, but apps may choose to impose additional checks based on their use cases. For example, if I have an ecommerce store and I allow users to pay with their Ethereum wallets, I might choose to use the `dataCallback` capability to request a shipping address. Furthermore, I might use the `validationURL` to ensure users who live in countries I don't ship to don't end up submitting payments onchain.
##### App
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: '...',
required: true
}
{
type: '...',
required: false
}
],
collectionURL: 'https://...',
validationURL: 'https://...'
}
}
})
```
Assuming it is capable of providing the requested information, the wallet will submit a `POST` request to the supplied `validationURL` according to the specification in the following section **before** the calls are submitted onchain. The app MUST respond to requests to its validation URL as follows:
```typescript!
type ValidationResponse = {
valid: boolean
reason?: string
}
```
If the app responds with `{valid: false}`, the wallet will not let the user submit the calls onchain. The app MAY also include a reason why the submitted information is invalid, but it is up to the wallet to display it to the user or not.
##### Wallet
If a `wallet_sendCalls` request with a `dataCallback` capability includes a `validationURL`, the wallet MUST `POST` the collected information to the specified URL as specified below before allowing the user to submit the calls onchain.
If the app's `validationURL` responds with `{valid: false}`, the wallet MUST NOT allow the user to submit the calls onchain. The wallet MAY choose to display the app's supplied `reason` for invalidity.
###### Specification
```typescript!
type DataCallbackValidationParams = {
dataCallback: Record<string, any> // Keys are the requested data types, and values are the corresponding values supplied by the wallet
}
```
###### Example
```typescript!
fetch(capabilities.dataCallback.validationURL, {
method: 'POST',
body: JSON.stringify({
dataCallback: {
email: 'foo@bar.com',
phoneNumber: {
country: 'US',
number: '123-456-7890'
}
},
walletAddress: '0x...'
})
})
```
#### `updateURL`
The `updateURL` allows apps to receive requested information and optionally update the `calls` the wallet will submit onchain based on that information.
Continuing with the ecommerce example, an app developer could use the `updateURL` to receive the payer's shipping address, and then update the `calls` to adjust for shipping cost.
##### App
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: '...',
required: true
}
{
type: '...',
required: false
}
],
collectionURL: 'https://...',
updateURL: 'https://...'
}
}
})
```
Assuming it is capable of providing the requested information, the wallet will submit a `POST` request to the supplied `updateURL` according to the specification in the following section **before** the calls are submitted onchain. The app MUST respond to requests to its update URL as follows:
```typescript!
type UpdateResponse = {
calls: {
to: `0x${string}`
data: `0x${string}`
value: `0x${string}`
}[]
}
```
The wallet will then use the calls supplied by this endpoint instead of the ones initially submitted as part of the `wallet_sendCalls` request.
Note that the `calls` response is required. I.e. even if there is not update to the calls, the endpoint should respond with the same calls it received.
##### Wallet
If a `wallet_sendCalls` request with a `dataCallback` capability includes an `updateURL`, the wallet MUST `POST` the collected information to the specified URL as specified below before allowing the user to submit the calls onchain.
The wallet MUST then use the calls returned by the endpoint instead of the ones initially received in the `wallet_sendCalls` request.
###### Specification
```typescript!
type DataCallbackUpdateParams = {
dataCallback: Record<string, any> // Keys are the requested data types, and values are the corresponding values supplied by the wallet
walletAddress: `0x${string}` // The address of the account that submitted the calls onchain
}
```
###### Example
```typescript!
fetch(capabilities.dataCallback.updateURL, {
method: 'POST',
body: JSON.stringify({
dataCallback: {
email: 'foo@bar.com',
phoneNumber: {
country: 'US',
number: '123-456-7890'
}
},
walletAddress: '0x...'
})
})
```
### Email, Phone Number, and Shipping Address Request Types
As part of this proposal, we define three data request types. When submitting to requesting applications, wallets MUST format data as specified below.
#### Email
Apps can request a user's email with an `email` request type.
##### Example
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: 'email',
required: true
}
],
collectionURL: 'https://...',
}
}
})
```
Wallets MUST provide emails in the form of a single string.
##### Example
```typescript!
fetch(capabilities.dataCallback.collectionURL, {
method: 'POST',
body: JSON.stringify({
callsId: '...',
statusURL: 'server.wallet.com',
dataCallback: {
email: 'foo@bar.com'
},
walletAddress: '0x...'
})
})
```
#### Phone Number
Apps can request a user's phone number with a `phoneNumber` request type.
##### Example
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: 'phoneNumber',
required: true
}
],
collectionURL: 'https://...',
}
}
})
```
Wallets MUST provide phone numbers in the form of an object with string `country` and `number` fields, where `country` is a country's two letter ISO 3166 code and `number` is the unformatted phone number.
##### Example
```typescript!
fetch(capabilities.dataCallback.collectionURL, {
method: 'POST',
body: JSON.stringify({
callsId: '...',
statusURL: 'server.wallet.com',
dataCallback: {
phoneNumber: {
country: 'US',
number: '1234567890'
}
},
walletAddress: '0x...'
})
})
```
#### Shipping Address
Apps can request a user's shipping address with a `shippingAddress` request type.
##### Example
```typescript!
wallet_sendCalls({
...
capabilities: {
...
dataCallback: {
request: [
{
type: 'shippingAddress',
required: true
}
],
collectionURL: 'https://...',
}
}
})
```
Wallets MUST provide shipping addresses in the form of an object with string `address1`, `address2`, `city`, `stateOrProvince`, `postalCode`, and `country` fields, where `country` is a country's two letter ISO 3166 code.
##### Example
```typescript!
fetch(capabilities.dataCallback.collectionURL, {
method: 'POST',
body: JSON.stringify({
callsId: '...',
statusURL: 'server.wallet.com',
dataCallback: {
shippingAddress: {
address1: '...',
address2: '...',
city: '...',
stateOrProvince: '...',
postalCode: '...',
country: '...'
}
},
walletAddress: '0x...'
})
})
```
## Open Questions
- What happens if data callback request fails? User has already submitted calls onchain.
- Consent. When `validationURL` and / or `updateURL` are present, user should probably need to explicitly consent to sharing info before wallet submits it to the app.
- Best data structure for global shipping addresses?
- Should we just use JSON-RPC to send info to apps too?