Getting started with the Web API

Stay organized with collections Save and categorize content based on your preferences.

Before getting started with the Web API, make sure you’ve completed the prerequisites. To continue with the Web API, you must have a service account and a service account key, and you are required to grant access to your service account to call the Passes API.

Download the sample code on GitHub to run the code snippets referenced in the steps below.

Authentication and authorization

Requests to the Google Wallet API must be authenticated so that the Google Wallet API can identify that the request is being made by your application. This is achieved by using the service account key to obtain an access token.

First, perform the necessary library imports and define some variables for the service account JSON, and IDs for the issuer, class, unique user and object that will be saved.

Java

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.*;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonParser;
import com.google.api.client.json.gson.GsonFactory;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.common.collect.Lists;

import java.io.*;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;

// Only include if you are using the Google Wallet client library
// https://developers.google.com/wallet/retail/loyalty-cards/resources/libraries
import com.google.api.services.walletobjects.Walletobjects;
import com.google.api.services.walletobjects.model.*;

public class DemoOffer {
  public static void main(String[] args) throws Exception {
    /*
     * keyFilePath - Path to service account key file from Google Cloud Console
     * - Environment variable: GOOGLE_APPLICATION_CREDENTIALS
     */
    final String keyFilePath = System.getenv().getOrDefault(
        "GOOGLE_APPLICATION_CREDENTIALS",
        "/path/to/key.json");

    /*
     * issuerId - The issuer ID being updated in this request
     * - Environment variable: WALLET_ISSUER_ID
     */
    String issuerId = System.getenv().getOrDefault(
        "WALLET_ISSUER_ID",
        "issuer-id");

    /*
     * classId - Developer-defined ID for the wallet class
     * - Environment variable: WALLET_CLASS_ID
     */
    String classId = System.getenv().getOrDefault(
        "WALLET_CLASS_ID",
        "test-offer-class-id");

    /*
     * userId - Developer-defined ID for the user, such as an email address
     * - Environment variable: WALLET_USER_ID
     */
    String userId = System.getenv().getOrDefault(
        "WALLET_USER_ID",
        "user-id");

    /*
     * objectId - ID for the wallet object
     * - Format: `issuerId.identifier`
     * - Should only include alphanumeric characters, '.', '_', or '-'
     * - `identifier` is developer-defined and unique to the user
     */
    String objectId = String.format("%s.%s-%s",
        issuerId, userId.replaceAll("[^\\w.-]", "_"), classId);

PHP

use Firebase\JWT\JWT;
use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Auth\Middleware\AuthTokenMiddleware;
use Google\Client as Google_Client;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Exception\ClientException;


/*
 * keyFilePath - Path to service account key file from Google Cloud Console
 *             - Environment variable: GOOGLE_APPLICATION_CREDENTIALS
 */
$keyFilePath = getenv('GOOGLE_APPLICATION_CREDENTIALS') ?: '/path/to/key.json';

/*
 * issuerId - The issuer ID being updated in this request
 *          - Environment variable: WALLET_ISSUER_ID
 */
$issuerId = getenv('WALLET_ISSUER_ID') ?: 'issuer-id';

/*
 * classId - Developer-defined ID for the wallet class
 *         - Environment variable: WALLET_CLASS_ID
 */
$classId = getenv('WALLET_CLASS_ID') ?: 'test-offer-class-id';

/*
 * userId - Developer-defined ID for the user, such as an email address
 *        - Environment variable: WALLET_USER_ID
 */
$userId = getenv('WALLET_USER_ID') ?: 'user-id';

/*
 * objectId - ID for the wallet object
 *          - Format: `issuerId.identifier`
 *          - Should only include alphanumeric characters, '.', '_', or '-'
 *          - `identifier` is developer-defined and unique to the user
 */
$objectId = "{$issuerId}." . preg_replace('/[^\w.-]/i', '_', $userId) . "-{$classId}";

Python

import json
import os
import re
import uuid

from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
from google.auth import jwt, crypt

# KEY_FILE_PATH - Path to service account key file from Google Cloud Console
#               - Environment variable: GOOGLE_APPLICATION_CREDENTIALS
KEY_FILE_PATH = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS",
                               "/path/to/key.json")

# ISSUER_ID - The issuer ID being updated in this request
#           - Environment variable: WALLET_ISSUER_ID
ISSUER_ID = os.environ.get("WALLET_ISSUER_ID", "issuer-id")

# CLASS_ID - Developer-defined ID for the wallet class
#         - Environment variable: WALLET_CLASS_ID
CLASS_ID = os.environ.get("WALLET_CLASS_ID", "test-offer-class-id")

# USER_ID - Developer-defined ID for the user, such as an email address
#        - Environment variable: WALLET_USER_ID
USER_ID = os.environ.get("WALLET_USER_ID", "test@example.com")

# objectId - ID for the wallet object
#          - Format: `issuerId.identifier`
#          - Should only include alphanumeric characters, '.', '_', or '-'
#          - `identifier` is developer-defined and unique to the user
OBJECT_ID = "%s.%s-%s" % (ISSUER_ID, re.sub(r"[^\w.-]", "_", USER_ID), CLASS_ID)

C#

using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using Google.Apis.Auth.OAuth2;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;


class DemoOffer
{
  /*
  * keyFilePath - Path to service account key file from Google Cloud Console
  *             - Environment variable: GOOGLE_APPLICATION_CREDENTIALS
  */
  static string keyFilePath = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS") ?? "/path/to/key.json";

  /*
  * issuerId - The issuer ID being used in this request
  *          - Environment variable: WALLET_ISSUER_ID
  */
  static string issuerId = Environment.GetEnvironmentVariable("WALLET_ISSUER_ID") ?? "issuer-id";

  /*
  * classId - Developer-defined ID for the wallet class
  *         - Environment variable: WALLET_CLASS_ID
  */
  static string classId = Environment.GetEnvironmentVariable("WALLET_CLASS_ID") ?? "test-offer-class-id";

  /*
  * userId - Developer-defined ID for the user, such as an email address
  *        - Environment variable: WALLET_USER_ID
  */
  static string userId = Environment.GetEnvironmentVariable("WALLET_USER_ID") ?? "user-id";

  /*
  * objectId - ID for the wallet object
  *          - Format: `issuerId.identifier`
  *          - Should only include alphanumeric characters, '.', '_', or '-'
  *          - `identifier` is developer-defined and unique to the user
  */
  static string objectId = $"{issuerId}.{new Regex(@"[^\w.-]", RegexOptions.Compiled).Replace(userId, "_")}-{classId}";

Node.js

const { GoogleAuth } = require('google-auth-library');
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');

/*
 * keyFilePath - Path to service account key file from Google Cloud Console
 *             - Environment variable: GOOGLE_APPLICATION_CREDENTIALS
 */
const keyFilePath = process.env.GOOGLE_APPLICATION_CREDENTIALS || '/path/to/key.json';

/*
 * issuerId - The issuer ID being updated in this request
 *          - Environment variable: WALLET_ISSUER_ID
 */
const issuerId = process.env.WALLET_ISSUER_ID || 'issuer-id';

/*
 * classId - Developer-defined ID for the wallet class
 *         - Environment variable: WALLET_CLASS_ID
 */
const classId = process.env.WALLET_CLASS_ID || 'test-offer-class-id';

/*
 * userId - Developer-defined ID for the user, such as an email address
 *        - Environment variable: WALLET_USER_ID
 */
let userId = process.env.WALLET_USER_ID || 'user-id';

/*
 * objectId - ID for the wallet object
 *          - Format: `issuerId.identifier`
 *          - Should only include alphanumeric characters, '.', '_', or '-'
 *          - `identifier` is developer-defined and unique to the user
 */
let objectId = `${issuerId}.${userId.replace(/[^\w.-]/g, '_')}-${classId}`;

Next, use one of the framework libraries to retrieve the necessary credentials to call the Google Wallet API.

Java

GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(keyFilePath))
    .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/wallet_object.issuer"));
credentials.refresh();

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
HttpRequestFactory httpRequestFactory = httpTransport.createRequestFactory(new HttpCredentialsAdapter(credentials));

PHP

$credentials = new ServiceAccountCredentials(
  'https://www.googleapis.com/auth/wallet_object.issuer',
  $keyFilePath
);

$middleware = new AuthTokenMiddleware($credentials);
$stack = HandlerStack::create();
$stack->push($middleware);
$httpClient = new Client([
  'handler' => $stack,
  'auth' => 'google_auth'
]);

Python

credentials = service_account.Credentials.from_service_account_file(
    KEY_FILE_PATH,
    scopes=["https://www.googleapis.com/auth/wallet_object.issuer"])

http_client = AuthorizedSession(credentials)

C#

credentials = (ServiceAccountCredential)GoogleCredential.FromFile(keyFilePath)
    .CreateScoped(new[] { "https://www.googleapis.com/auth/wallet_object.issuer" })
    .UnderlyingCredential;

httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer",
    await credentials.GetAccessTokenForRequestAsync()
);

Node.js

const credentials = require(keyFilePath);

const httpClient = new GoogleAuth({
  credentials: credentials,
  scopes: 'https://www.googleapis.com/auth/wallet_object.issuer'
});

Creating a Passes Object

A Passes Object is an instance of a Passes Class. In order to create a Passes Object, you must provide the following attributes:

  • classId: The id of the Passes Class
  • id: A unique id for your customer Refer to the template guidelines for more information on how these attributes are represented in the Offer.

Code sample to create a Passes Object:

HTTP

POST /walletobjects/v1/offerObject HTTP/1.1
Host: walletobjects.googleapis.com
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>;
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

{
  "id": "issuer-id.user-id",
  "classId": "issuer-id.class-id",
  "heroImage": {
    "sourceUri": {
      "uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
      "description": "Test heroImage description"
    }
  },
  "textModulesData": [
    {
      "header": "Test text module header",
      "body": "Test text module body"
    }
  ],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "http://maps.google.com/",
        "description": "Test link module uri description"
      },
      {
        "kind": "walletobjects#uri",
        "uri": "tel:6505555555",
        "description": "Test link module tel description"
      }
    ]
  },
  "imageModulesData": [
    {
      "mainImage": {
        "kind": "walletobjects#image",
        "sourceUri": {
          "kind": "walletobjects#uri",
          "uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          "description": "Test image module description"
        }
      }
    }
  ],
  "barcode": {
    "type": "qrCode",
    "value": "Testing Offers QR Code"
  },
  "state": "active",
  "validTimeInterval": {
    "kind": "walletobjects#timeInterval",
    "start": {
      "date": "2023-06-12T23:20:50.52Z"
    },
    "end": {
      "date": "2023-12-12T23:20:50.52Z"
    }
  },
  "locations": [
    {
      "kind": "walletobjects#latLongPoint",
      "latitude": 37.424015499999996,
      "longitude": -122.09259560000001
    }
  ]
}

Java

HttpRequest objectRequest;
HttpResponse objectResponse;

GenericUrl objectUrl = new GenericUrl(
    "https://walletobjects.googleapis.com/walletobjects/v1/offerObject/" + objectId);
String objectPayload = String.format(
    "{"
  + "  \"id\": \"%s\","
  + "  \"classId\": \"%s.%s\","
  + "  \"heroImage\": {"
  + "    \"sourceUri\": {"
  + "      \"uri\": \"https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg\","
  + "      \"description\": \"Test heroImage description\""
  + "    }"
  + "  },"
  + "  \"textModulesData\": ["
  + "    {"
  + "      \"header\": \"Test text module header\","
  + "      \"body\": \"Test text module body\""
  + "    }"
  + "  ],"
  + "  \"linksModuleData\": {"
  + "    \"uris\": ["
  + "      {"
  + "        \"kind\": \"walletobjects#uri\","
  + "        \"uri\": \"http://maps.google.com/\","
  + "        \"description\": \"Test link module uri description\""
  + "      },"
  + "      {"
  + "        \"kind\": \"walletobjects#uri\","
  + "        \"uri\": \"tel:6505555555\","
  + "        \"description\": \"Test link module tel description\""
  + "      }"
  + "    ]"
  + "  },"
  + "  \"imageModulesData\": ["
  + "    {"
  + "      \"mainImage\": {"
  + "        \"kind\": \"walletobjects#image\","
  + "        \"sourceUri\": {"
  + "          \"kind\": \"walletobjects#uri\","
  + "          \"uri\": \"http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg\","
  + "          \"description\": \"Test image module description\""
  + "        }"
  + "      }"
  + "    }"
  + "  ],"
  + "  \"barcode\": {"
  + "    \"type\": \"qrCode\","
  + "    \"value\": \"Testing Offers QR Code\""
  + "  },"
  + "  \"state\": \"active\","
  + "  \"validTimeInterval\": {"
  + "    \"kind\": \"walletobjects#timeInterval\","
  + "    \"start\": {"
  + "      \"date\": \"2023-06-12T23:20:50.52Z\""
  + "    },"
  + "    \"end\": {"
  + "      \"date\": \"2023-12-12T23:20:50.52Z\""
  + "    }"
  + "  },"
  + "  \"locations\": ["
  + "    {"
  + "      \"kind\": \"walletobjects#latLongPoint\","
  + "      \"latitude\": 37.424015499999996,"
  + "      \"longitude\": -122.09259560000001"
  + "    }"
  + "  ]"
  + "}", objectId, issuerId, classId);

try {
  // Create and send the request
  objectRequest = httpRequestFactory.buildGetRequest(objectUrl);
  objectResponse = objectRequest.execute();

  System.out.println("object GET response: " + objectResponse.parseAsString());
} catch (HttpResponseException ex) {
  if (ex.getStatusCode() == 404) {
    // Object does not yet exist
    // Send POST request to create it
    // Convert body to JSON
    JsonParser objectParser = GsonFactory.getDefaultInstance().createJsonParser(objectPayload);
    GenericJson objectJson = objectParser.parseAndClose(GenericJson.class);
    HttpContent objectBody = new JsonHttpContent(GsonFactory.getDefaultInstance(), objectJson);

    // Create and send the request
    objectRequest = httpRequestFactory.buildPostRequest(objectUrl, objectBody);
    objectResponse = objectRequest.execute();

    System.out.println("object POST response: " + objectResponse.parseAsString());
  } else {
    // Something else went wrong
    throw ex;
  }
}

PHP

$objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/offerObject/";
$objectPayload = <<<EOD
{
  "id": "{$objectId}",
  "classId": "{$issuerId}.{$classId}",
  "heroImage": {
    "sourceUri": {
      "uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
      "description": "Test heroImage description"
    }
  },
  "textModulesData": [
    {
      "header": "Test text module header",
      "body": "Test text module body"
    }
  ],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "http://maps.google.com/",
        "description": "Test link module uri description"
      },
      {
        "kind": "walletobjects#uri",
        "uri": "tel:6505555555",
        "description": "Test link module tel description"
      }
    ]
  },
  "imageModulesData": [
    {
      "mainImage": {
        "kind": "walletobjects#image",
        "sourceUri": {
          "kind": "walletobjects#uri",
          "uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          "description": "Test image module description"
        }
      }
    }
  ],
  "barcode": {
    "type": "qrCode",
    "value": "Testing Offers QR Code"
  },
  "state": "active",
  "validTimeInterval": {
    "kind": "walletobjects#timeInterval",
    "start": {
      "date": "2023-06-12T23:20:50.52Z"
    },
    "end": {
      "date": "2023-12-12T23:20:50.52Z"
    }
  },
  "locations": [
    {
      "kind": "walletobjects#latLongPoint",
      "latitude": 37.424015499999996,
      "longitude": -122.09259560000001
    }
  ]
}
EOD;

try {
  $objectResponse = $httpClient->get($objectUrl . $objectId);
} catch (ClientException $err) {
  if ($err->getResponse()->getStatusCode() == 404) {
    // Object does not yet exist
    // Send POST request to create it
    $objectResponse = $httpClient->post(
      $objectUrl,
      ['json' => json_decode($objectPayload)]
    );
  }
}

echo 'object GET or POST response: ' . $objectResponse->getBody();

Python

OBJECT_URL = "https://walletobjects.googleapis.com/walletobjects/v1/offerObject/"
object_payload = {
    "id": OBJECT_ID,
    "classId": f"{ISSUER_ID}.{CLASS_ID}",
    "heroImage": {
        "sourceUri": {
            "uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
            "description": "Test heroImage description"
        }
    },
    "textModulesData": [
        {
            "header": "Test text module header",
            "body": "Test text module body"
        }
    ],
    "linksModuleData": {
        "uris": [
            {
                "kind": "walletobjects#uri",
                "uri": "http://maps.google.com/",
                "description": "Test link module uri description"
            },
            {
                "kind": "walletobjects#uri",
                "uri": "tel:6505555555",
                "description": "Test link module tel description"
            }
        ]
    },
    "imageModulesData": [
        {
            "mainImage": {
                "kind": "walletobjects#image",
                "sourceUri": {
                    "kind": "walletobjects#uri",
                    "uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
                    "description": "Test image module description"
                }
            }
        }
    ],
    "barcode": {
        "type": "qrCode",
        "value": "Testing Offers QR Code"
    },
    "state": "active",
    "validTimeInterval": {
        "kind": "walletobjects#timeInterval",
        "start": {
            "date": "2023-06-12T23:20:50.52Z"
        },
        "end": {
            "date": "2023-12-12T23:20:50.52Z"
        }
    },
    "locations": [
        {
            "kind": "walletobjects#latLongPoint",
            "latitude": 37.424015499999996,
            "longitude": -122.09259560000001
        }
    ]
}

object_response = http_client.get(OBJECT_URL + OBJECT_ID)
if object_response.status_code == 404:
    # Object does not yet exist
    # Send POST request to create it
    object_response = http_client.post(
        OBJECT_URL,
        json=object_payload
    )

print("object GET or POST response:", object_response.text)

C#

string objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/offerObject/";
var objectPayload = new
{
  id = objectId,
  classId = $"{issuerId}.{classId}",
  heroImage = new
  {
    sourceUri = new
    {
      uri = "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
      description = "Test heroImage description"
    }
  },
  textModulesData = new object[]
  {
    new
    {
      header = "Test text module header",
      body = "Test text module body"
    }
  },
  linksModuleData = new
  {
    uris = new object[]
    {
      new
      {
        kind = "walletobjects#uri",
        uri = "http://maps.google.com/",
        description = "Test link module uri description"
      },
      new
      {
        kind = "walletobjects#uri",
        uri = "tel:6505555555",
        description = "Test link module tel description"
      }
    }
  },
  imageModulesData = new object[]
  {
    new
    {
      mainImage = new
      {
        kind = "walletobjects#image",
        sourceUri = new
        {
          kind = "walletobjects#uri",
          uri = "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          description = "Test image module description"
        }
      }
    }
  },
  barcode = new
  {
    type = "qrCode",
    value = "Testing Offers QR Code"
  },
  state = "active",
  validTimeInterval = new
  {
    kind = "walletobjects#timeInterval",
    start = new
    {
      date = "2023-06-12T23:20:50.52Z"
    },
    end = new
    {
      date = "2023-12-12T23:20:50.52Z"
    }
  },
  locations = new object[]
  {
    new
    {
      kind = "walletobjects#latLongPoint",
      latitude = 37.424015499999996,
      longitude = -122.09259560000001
    }
  }
};

HttpRequestMessage objectRequest = new HttpRequestMessage(HttpMethod.Get, $"{objectUrl}{objectId}");
HttpResponseMessage objectResponse = httpClient.Send(objectRequest);
if (objectResponse.StatusCode == HttpStatusCode.NotFound)
{
  // Object does not yet exist
  // Send POST request to create it
  objectRequest = new HttpRequestMessage(HttpMethod.Post, objectUrl);
  objectRequest.Content = new StringContent(JsonConvert.SerializeObject(objectPayload));
  objectResponse = httpClient.Send(objectRequest);
}

string objectContent = await objectResponse.Content.ReadAsStringAsync();
Console.WriteLine($"object GET or POST response: {objectContent}");

Node.js

const objectUrl = 'https://walletobjects.googleapis.com/walletobjects/v1/offerObject/';
const objectPayload = {
  "id": objectId,
  "classId": `${issuerId}.${classId}`,
  "heroImage": {
    "sourceUri": {
      "uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
      "description": "Test heroImage description"
    }
  },
  "textModulesData": [
    {
      "header": "Test text module header",
      "body": "Test text module body"
    }
  ],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "http://maps.google.com/",
        "description": "Test link module uri description"
      },
      {
        "kind": "walletobjects#uri",
        "uri": "tel:6505555555",
        "description": "Test link module tel description"
      }
    ]
  },
  "imageModulesData": [
    {
      "mainImage": {
        "kind": "walletobjects#image",
        "sourceUri": {
          "kind": "walletobjects#uri",
          "uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          "description": "Test image module description"
        }
      }
    }
  ],
  "barcode": {
    "type": "qrCode",
    "value": "Testing Offers QR Code"
  },
  "state": "active",
  "validTimeInterval": {
    "kind": "walletobjects#timeInterval",
    "start": {
      "date": "2023-06-12T23:20:50.52Z"
    },
    "end": {
      "date": "2023-12-12T23:20:50.52Z"
    }
  },
  "locations": [
    {
      "kind": "walletobjects#latLongPoint",
      "latitude": 37.424015499999996,
      "longitude": -122.09259560000001
    }
  ]
};
let objectResponse;

try {
  objectResponse = await httpClient.request({
    url: objectUrl + objectId,
    method: 'GET'
  });
} catch (err) {
  if (err.response && err.response.status === 404) {
    // Object does not yet exist
    // Send POST request to create it
    objectResponse = await httpClient.request({
      url: objectUrl,
      method: 'POST',
      data: objectPayload
    });
  } else {
    objectResponse = err;
  }
}

console.log('object GET or POST response:', objectResponse);

Once complete, your customer’s Passes Object will have been created on the server. However, at this stage, the Passes object has not been linked to a Google user or their device. For the pass to be associated with a Google Wallet user, the user must first add the pass to Google Wallet.

Adding to Google Wallet

Adding a pass to Google Wallet links the Passes Object to a Google user and can only be initiated in the context of a logged in Google identity. This can be achieved by directing the user to a Add to Google Wallet URL.

The Add to Google Wallet URL is a dynamically generated URL that contains the following information about the Passes Object id created in the previous step. This information is encoded as a JSON Web Token (JWT).

JSON Web Token (JWT)

The JWT contains claims that you (the issuer) is making about the Passes Object that will be saved by the user. The JWT must be signed using the private_key from the service account key obtained from the Create Service Account step, and Google will validate these claims by verifying the JWT signature.

The JWT claims should have the following structure:

{
  "aud": "google",
  "origins": ["https://example.com"],
  "iss": "my-service-account@my-project-id.iam.gserviceaccount.com",
  "typ": "savetowallet",
  "payload": {
    "offerObjects": [
      {
        "id": "PASSES_OBJECT_ID_1234567890"
      }
    ]
  }
}

Construct the JWT claims (step 1), and obtain the token by signing the claims with the service account key's private_key (step 2):

Java

HashMap<String, String> objectIdMap = new HashMap<String, String>();
objectIdMap.put("id", objectId);

HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put("offerObjects", new ArrayList<>(Arrays.asList(objectIdMap)));

HashMap<String, Object> claims = new HashMap<String, Object>();
claims.put("iss", ((ServiceAccountCredentials) credentials).getClientEmail());
claims.put("aud", "google");
claims.put("origins", new ArrayList<>(Arrays.asList("www.example.com")));
claims.put("typ", "savetowallet");
claims.put("payload", payload);

Algorithm algorithm = Algorithm.RSA256(
    null,
    (RSAPrivateKey) ((ServiceAccountCredentials) credentials).getPrivateKey());
String token = JWT.create()
    .withPayload(claims)
    .sign(algorithm);
String saveUrl = "https://pay.google.com/gp/v/save/" + token;

System.out.println(saveUrl);

PHP

$serviceAccount = json_decode(file_get_contents($keyFilePath), true);
$claims = [
  'iss' => $serviceAccount['client_email'],
  'aud' => 'google',
  'origins' => ['www.example.com'],
  'typ' => 'savetowallet',
  'payload' => [
    'offerObjects' => [
      ['id' => $objectId]
    ]
  ]
];

$token = JWT::encode($claims, $serviceAccount['private_key'], 'RS256');
$saveUrl = "https://pay.google.com/gp/v/save/${token}";

echo $saveUrl;

Python

claims = {
    "iss": http_client.credentials.service_account_email,
    "aud": "google",
    "origins": ["www.example.com"],
    "typ": "savetowallet",
    "payload": {
        "offerObjects": [
            {
                "id": OBJECT_ID
            }
        ]
    }
}

signer = crypt.RSASigner.from_service_account_file(KEY_FILE_PATH)
token = jwt.encode(signer, claims).decode("utf-8")
save_url = f"https://pay.google.com/gp/v/save/{token}"

print(save_url)

C#

JwtPayload claims = new JwtPayload();
claims.Add("iss", credentials.Id);
claims.Add("aud", "google");
claims.Add("origins", new string[] { "www.example.com" });
claims.Add("typ", "savetowallet");
claims.Add("payload", new
{
  offerObjects = new object[]
  {
    new
    {
      id = objectId
    }
  }
});

RsaSecurityKey key = new RsaSecurityKey(credentials.Key);
SigningCredentials signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
JwtSecurityToken jwt = new JwtSecurityToken(new JwtHeader(signingCredentials), claims);
string token = new JwtSecurityTokenHandler().WriteToken(jwt);
string saveUrl = $"https://pay.google.com/gp/v/save/{token}";

Console.WriteLine(saveUrl);

Node.js

const claims = {
  iss: credentials.client_email,
  aud: 'google',
  origins: ['www.example.com'],
  typ: 'savetowallet',
  payload: {
    offerObjects: [{
      id: objectId
    }],
  }
};

const token = jwt.sign(claims, credentials.private_key, { algorithm: 'RS256' });
const saveUrl = `https://pay.google.com/gp/v/save/${token}`;

console.log(saveUrl);

After you’ve obtained a signed JWT, you can use this information to construct a Add to Google Wallet link.

The Add to Google Wallet link has the following format:

https://pay.google.com/gp/v/save/{token}

This link can be embedded into your web page, or email as a hyperlink. It can also be sent to the customer using other channels like chat and SMS.

The safe length of an encoded JWT is 1800 characters. If your JWT is below this limit, the entire object can be included in the signed JWT. Your JWTs should remain below this limit. If the length is over 1800 characters, the save may not work due to truncation by web browsers.

Add to Google Wallet Button

For best results, leverage the Google Wallet button assets in your web page, email, or Android application.

The Google Wallet button can be rendered in two ways:

  • The JavaScript Web button can be used for websites.
  • The JWT link with a Google Wallet button can be used for email, SMS, and apps, and websites.

Next steps

  • Update an Offer
  • Customize the appearance of your Offer
  • Still have questions? Please review our FAQ.