This guide explains how to use callbacks with the Google Wallet API. When a
pass is created or deleted, Google can perform a callback to an HTTPS
endpoint of your choosing.

This callback is class-specific, and includes data
about the event such as the class, object, and event type.

This can be used to
keep track of the number of user adds and deletions that occur. For
example, callbacks can be configured to send events to an analytics
application to track customer engagement during promotional events.

## Prerequisites

Before you start, review the following prerequisites:

- Stand up an HTTPS endpoint that handles POST requests. This endpoint needs to be publicly available.
- Programmatically update the callback endpoint for each class. See the [`callbackOptions`](https://developers.google.com/wallet/tickets/events/rest/v1/CallbackOptions) property by class in the REST API.
- Recommended: Use the [Tink](https://developers.google.com/tink) library to verify the signatures.

## Implement callbacks


For every add or delete performed by the user on an

an object

, Google makes callbacks to the merchants with details about the add or delete on a

per-class

URL. Merchants need to first use the Public Keys to verify the authenticity of
the message. After the callbacks verify the message, the callbacks can be used
for downstream operations.

## Verify the signature


We recommend that you use the Tink library to verify the message signature
when you implement your HTTPS endpoint. The
[Tink library](https://github.com/google/tink)
provides `PaymentMethodTokenRecipient`, a utility that
automatically verifies the signature and returns the actual message upon
successful verification.


The following example shows how to use the Tink library to implement
`PaymentMethodTokenRecipient`:

```
import java.io.IOException;
import javax.servlet.http.*;
import com.google.common.io.CharStreams;
import com.google.crypto.tink.apps.paymentmethodtoken.*;

// Replace ISSUER_ID with your issuer id
private static final String RECIPIENT_ID = "ISSUER_ID";

private static final String PUBLIC_KEY_URL = "https://pay.google.com/gp/m/issuer/keys";
private static final String SENDER_ID = "GooglePayPasses";
private static final String PROTOCOL = "ECv2SigningOnly";

private static final GooglePaymentsPublicKeysManager keysManager = new GooglePaymentsPublicKeysManager.Builder()
        .setKeysUrl(PUBLIC_KEY_URL)
        .build();

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
  try {
    // Extract signed message with signature from POST request body.
    String signedMessage = CharStreams.toString(request.getReader());
    PaymentMethodTokenRecipient recipient =
            new PaymentMethodTokenRecipient.Builder()
                    .protocolVersion(PROTOCOL)
                    .fetchSenderVerifyingKeysWith(keysManager)
                    .senderId(SENDER_ID)
                    .recipientId(RECIPIENT_ID)
                    .build();

    String serializedJsonMessage = recipient.unseal(signedMessage);

    // Use serializedJsonMessage to extract the details
  } catch (Exception e) {
    // Handle the error
  }
}
```

## Expected message format


The message format is JSON that's serialized into a string with the following
properties:

| Identifier | Description |
|---|---|
| `classId` | Fully qualified class ID. Uses the following format: ``` <issuer_id.class_id> ``` |
| `objectId` | Fully qualified object ID. Uses the following format: ``` <issuer_id.object_id> ``` |
| `expTimeMillis` | Expiration time in milliseconds since EPOCH. After the expiration time, the message needs to be deemed invalid. |
| `eventType` | Can be either `del` or `save` for `DELETE` and `SAVE`. |
| `nonce` | Nonce to track any duplicate deliveries. |

### Handle the request from a Google server


The following is a list of the key fields in the header of the request that's
sent to your callback endpoint:

- User-Agent: `Googlebot` or `Google-Valuables`
- Content-Type: `application/json`


Configure your server so that it doesn't reject the request. To do so, you can
set the following in `robots.txt`:

```
User-agent: Googlebot
Disallow:
```

> [!NOTE]
> **Note:** If you perform server-side checks on the `User-Agent` header, you should check if its value *contains* "Googlebot" or "Google-Valuables", not if it's an exact match.

### Retries


Callbacks are on a best-effort basis. Google will use common retry strategies
to be resilient in cases where the callback endpoint is not responding or has an
intermittent outage and will gracefully back off attempts.

### Duplicate deliveries


There might be duplicate deliveries in some cases. We recommend that you use
`nonce` to dedupe them.