For Google Chat apps built on HTTP endpoints, this section explains how to
verify that the requests to your endpoint come from Chat.

To dispatch interaction events to your Chat app's
endpoint, Google makes HTTPS requests to your service. To verify that the
request is coming from Google, Chat includes a Google-signed
OpenID Connect (OIDC)
[ID token](https://cloud.google.com/docs/authentication/token-types#identity-tokens)
as a
[bearer token](https://cloud.google.com/docs/authentication/token-types#bearer)
in the `Authorization` header of every HTTPS request (and in the
`authorizationEventObject.systemIdToken` field of the request body). For
example:

    POST
    Host: yourappurl.com
    Authorization: Bearer AbCdEf123456
    Content-Type: application/json
    User-Agent: Google-Dynamite

The string `AbCdEf123456` in the preceding example is the bearer authorization
token. This cryptographic token is signed by your
Chat app's unique, per-project service account
(`service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`),
and the `audience` field is set to the HTTP endpoint URL configured for your
Chat app when
[configuring the Chat app](https://developers.google.com/workspace/chat/configure-chat-api).

You can copy your Chat app's service account email
address from the **Connection settings** section of the Chat API
**Configuration** tab in the Google Cloud console:

1. In the Google Cloud console, go to **Menu**
   \> **APIs \& Services**
   \> **Enabled APIs \& Services**
   \> **Google Chat API**
   \> **Configuration**:

   [Go to Google Chat API Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat)
2. Under **Interactive features** \> **Connection settings** , copy the
   **Service account email** (`service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`).

> [!NOTE]
> **Note:** The per-project service account is provisioned after you save your Chat API configuration for the first time.

If you've implemented your Chat app using
Cloud Run functions or Cloud Run, Cloud IAM handles token verification
automatically when you grant the Chat app's service
account the `Cloud Run Invoker` (`roles/run.invoker`) role.
If your app implements its own HTTP server, you can verify the bearer token
using an open source [Google API client library](https://developers.google.com/workspace/chat/libraries):

- **Java** : <https://github.com/google/google-api-java-client>
- **Python** : <https://github.com/google/google-api-python-client>
- **Node.js** : <https://github.com/google/google-api-nodejs-client>
- **.NET** : <https://github.com/google/google-api-dotnet-client>

If the token doesn't verify for the Chat app, your
service should respond to the request with an HTTPS response code
`401 (Unauthorized)`.

## Authenticate requests using Cloud Run functions

If your function logic is implemented using Cloud Run functions or Cloud Run,
make sure that the HTTP endpoint URLs configured under **Triggers** in the
Chat app
[connection settings](https://developers.google.com/workspace/chat/configure-chat-api) correspond to the URL
of your Cloud Run function endpoint.

Then, authorize your Chat app's service account
(`service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`,
copied from the **Connection settings** section of the Chat API
**Configuration** tab) as an invoker using the following steps:

### Console

After deploying your function or service to Google Cloud:

1. In the Google Cloud console, go to the Cloud Run page:

   [Go to Cloud Run](https://console.cloud.google.com/run)
2. In the Cloud Run services list, click the checkbox next to the receiving
   function. (Don't click the function itself.)

3. Click **Permissions** at the top of the screen. The **Permissions** panel
   opens.

4. Click **Add principal**.

5. In the **New principals** field, enter your
   Chat app's service account email address
   (`service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`).

6. From the **Select a role** menu, select the role **Cloud Run**

   > **Cloud Run Invoker**.
7. Click **Save**.

### gcloud

Use the `gcloud functions add-invoker-policy-binding` command:

    gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
      --member='serviceAccount:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

Replace the following:

- `RECEIVING_FUNCTION`: the name of your Chat app's function.
- `PROJECT_NUMBER`: the project number from your Chat app's service account email address.

## Authenticate HTTP requests with an ID Token

For HTTP endpoints, the bearer authorization token in the request is a
Google-signed OpenID Connect (OIDC)
[ID token](https://cloud.google.com/docs/authentication/token-types#identity-tokens).
The `email` field is set to your Chat app's service
account email address
(`service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`),
and the `audience` field is set to the HTTP endpoint URL configured to receive
the request. For example, if the configured endpoint of your
Chat app is `https://example.com/app/`, then the
`audience` field in the ID token is `https://example.com/app/`.

This is the recommended authentication method if your HTTP endpoint isn't hosted
on a service that supports IAM-based authentication (such as Cloud Run).

The following samples show how to verify that the bearer token was issued by
Google for your Chat app and targeted at your app's
endpoint using the Google OAuth client library:

### Java

java/chat/secured-app/src/main/java/com/google/chat/app/secured/App.java [View on GitHub](https://github.com/googleworkspace/add-ons-samples/blob/main/java/chat/secured-app/src/main/java/com/google/chat/app/secured/App.java)

```java
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param event Event sent from Google Workspace add-on
 * @param authorization Authorization header from the request
 * @return {boolean} Whether the request is legitimate
 */
private boolean verifyAddOnRequest(JsonNode event, String authorization) throws Exception {
  JsonFactory factory = JacksonFactory.getDefaultInstance();

  GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
      .setAudience(Collections.singletonList(HTTP_ENDPOINT))
      .build();

  String bearer = authorization.substring("Bearer ".length(), authorization.length());
  GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
  return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(SERVICE_ACCOUNT_EMAIL);
}
```

### Python

python/chat/secured-app/main.py [View on GitHub](https://github.com/googleworkspace/add-ons-samples/blob/main/python/chat/secured-app/main.py)

```python
def verifyAddOnRequest() -> bool:
  """Determine whether a Google Workspace add-on request is legitimate.

  Args:
    request: Request sent from Google Workspace add-on

  Returns:
    Whether the request is legitimate
  """
  try:
    bearer = request.headers.get('Authorization')[len("Bearer "):]
    token = id_token.verify_oauth2_token(bearer, requests.Request(), HTTP_ENDPOINT)
    return token['email'] == SERVICE_ACCOUNT_EMAIL

  except:
    return False
```

### Node.js

node/chat/secured-app/index.js [View on GitHub](https://github.com/googleworkspace/add-ons-samples/blob/main/node/chat/secured-app/index.js)

```javascript
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param {Object} req Request sent from Google Workspace add-on
 * @return {boolean} Whether the request is legitimate
 */
async function verifyAddOnRequest(req) {
  try {
    const authorization = req.headers.authorization;
    const idToken = authorization.substring('Bearer '.length, authorization.length);
    const ticket = await new OAuth2Client().verifyIdToken({idToken, audience: HTTP_ENDPOINT});
    return ticket.getPayload().email_verified
        && ticket.getPayload().email === SERVICE_ACCOUNT_EMAIL;
  } catch (unused) {
    return false;
  }
}
```

## Related topics

- For an overview of authentication and authorization in Google Workspace, see [Learn about authentication and authorization](https://developers.google.com/workspace/guides/auth-overview).
- For an overview of authentication and authorization in Chat, see [Authentication overview](https://developers.google.com/workspace/chat/authenticate-authorize).
- Set up authentication and authorization with [user credentials](https://developers.google.com/workspace/chat/authenticate-authorize-chat-user) or a [service account](https://developers.google.com/workspace/chat/authenticate-authorize-chat-app).

## Chat apps that aren't add-ons: Verify requests from Google Chat

The following documentation applies to Chat apps that aren't
Google Workspace add-ons. To migrate a Chat app that isn't an
add-on, see
[Convert a Google Chat app to a Google Workspace add-on](https://developers.google.com/workspace/chat/convert).

For Chat apps that aren't add-ons configured with
**HTTP endpoint URL** under **Connection settings** , the type of the bearer
token and the value of the `audience` field depend on the type of
**Authentication Audience** you selected when
[configuring the Chat app](https://developers.google.com/workspace/chat/configure-chat-api),
and requests are signed by the shared service account
`chat@system.gserviceaccount.com`.

### Authenticate requests using Cloud Run functions (Chat apps that aren't add-ons)

If your function logic is implemented using Cloud Run functions, you
must select **HTTP endpoint URL** in the **Authentication Audience** field of
the Chat app
[connection setting](https://developers.google.com/workspace/chat/configure-chat-api) and make sure that the
HTTP endpoint URL in the configuration corresponds to the URL of the Cloud
Run function endpoint.

Then, you need to authorize the Google Chat service account
`chat@system.gserviceaccount.com` as an invoker using the following steps:

### Console

After deploying your function or service to Google Cloud:

1. In the Google Cloud console, go to the Cloud Run page:

   [Go to Cloud Run](https://console.cloud.google.com/run)
2. In the Cloud Run services list, click the checkbox next to the receiving
   function. (Don't click the function itself.)

3. Click **Permissions** at the top of the screen. The **Permissions** panel
   opens.

4. Click **Add principal**.

5. In the **New principals** field, enter `chat@system.gserviceaccount.com`.

6. From the **Select a role** menu, select the role **Cloud Run**

   > **Cloud Run Invoker**.
7. Click **Save**.

### gcloud

Use the `gcloud functions add-invoker-policy-binding` command:

    gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
      --member='serviceAccount:chat@system.gserviceaccount.com'

Replace `RECEIVING_FUNCTION` with the name of your
Chat app's function.

### Authenticate HTTP requests with an ID Token (Chat apps that aren't add-ons)

If the **Authentication Audience** field of the
Chat app that isn't an add-on
[connection setting](https://developers.google.com/workspace/chat/configure-chat-api) is set to
**HTTP endpoint URL** ,
the bearer authorization token in the request is a Google-signed OpenID Connect
(OIDC)
[ID token](https://cloud.google.com/docs/authentication/token-types#identity-tokens).
The `email` field is set to `chat@system.gserviceaccount.com`. The
**Authentication Audience** field is set to the URL you configured Google Chat
to send requests to your Chat app that isn't an
add-on. For example, if the configured endpoint of your
Chat app is `https://example.com/app/`, then the
**Authentication Audience** field in the ID token is `https://example.com/app/`.

The following samples show how to verify that the bearer token was issued by
Google Chat and targeted at your Chat app that isn't
an add-on using the Google OAuth client library.

### Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/java/basic-app/src/main/java/com/google/chat/app/basic/App.java)

```java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
        .setAudience(Collections.singletonList(AUDIENCE))
        .build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(CHAT_ISSUER);
```

### Python

python/basic-app/main.py [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/python/basic-app/main.py)

```python
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    token = id_token.verify_oauth2_token(bearer, request, AUDIENCE)
    return token['email'] == CHAT_ISSUER

except:
    return False
```

### Node.js

node/basic-app/index.js [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/node/basic-app/index.js)

```javascript
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by chatIssuer, intended for a third party.
try {
  const ticket = await client.verifyIdToken({
    idToken: bearer,
    audience: audience
  });
  return ticket.getPayload().email_verified
      && ticket.getPayload().email === chatIssuer;
} catch (unused) {
  return false;
}
```

### Authenticate requests with a Project Number JWT (Chat apps that aren't add-ons)

If the **Authentication Audience** field of the
Chat app that isn't an add-on
[connection setting](https://developers.google.com/workspace/chat/configure-chat-api) is set to `Project
Number`, the bearer authorization token in the request is a self-signed
[JSON Web Token (JWT)](https://cloud.google.com/docs/authentication/token-types#sa-jwts),
issued and signed by `chat@system.gserviceaccount.com`.
The `audience` field is set to the Google Cloud project number that you used
to build your Chat app that isn't an
add-on. For example, if your
Chat app's Cloud project number is
`1234567890`, then the `audience` field in the JWT is `1234567890`.

The following samples show how to verify that the bearer token was issued by
Google Chat and targeted at your project using the Google OAuth client library.

### Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/java/basic-app/src/main/java/com/google/chat/app/basic/App.java)

```java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GooglePublicKeysManager keyManagerBuilder =
    new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory)
        .setPublicCertsEncodedUrl(
            "https://www.googleapis.com/service_accounts/v1/metadata/x509/" + CHAT_ISSUER)
        .build();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(keyManagerBuilder).setIssuer(CHAT_ISSUER).build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.verifyAudience(Collections.singletonList(AUDIENCE))
    && idToken.verifyIssuer(CHAT_ISSUER);
```

### Python

python/basic-app/main.py [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/python/basic-app/main.py)

```python
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    certs_url = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/' + CHAT_ISSUER
    token = id_token.verify_token(bearer, request, AUDIENCE, certs_url)
    return token['iss'] == CHAT_ISSUER

except:
    return False
```

### Node.js

node/basic-app/index.js [View on GitHub](https://github.com/googleworkspace/google-chat-samples/blob/main/node/basic-app/index.js)

```javascript
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by CHAT_ISSUER, intended for a third party.
try {
  const response = await fetch('https://www.googleapis.com/service_accounts/v1/metadata/x509/' + chatIssuer);
  const certs = await response.json();
  await client.verifySignedJwtWithCertsAsync(
    bearer, certs, audience, [chatIssuer]);
  return true;
} catch (unused) {
  return false;
}
```