Push-уведомления (Dialogflow)

Посмотреть в Dialogflow

Нажмите Continue (Продолжить), чтобы импортировать пример Notifications в Dialogflow. Затем выполните следующие действия, чтобы развернуть и протестировать пример:

  1. Введите название агента и создайте новый агент Dialogflow для примера.
  2. После завершения импорта нажмите Перейти к агенту.
  3. В главном меню навигации выберите Выполнение заказов.
  4. Включите встроенный редактор и нажмите Развернуть. В редакторе появится образец кода.
  5. В главном меню навигации выберите Integrations (Интеграции), а затем нажмите Google Assistant (Google Ассистент).
  6. В появившемся модальном окне включите Auto-preview changes (Автоматический предпросмотр изменений) и нажмите Test (Протестировать), чтобы открыть симулятор действий.
  7. Введите Talk to my test app в симуляторе, чтобы протестировать пример.
Продолжить

Ваше действие может отправлять пользователям push-уведомления, когда это необходимо, например напоминание о приближении срока выполнения задачи.

В этом руководстве мы используем пример подсказок Actions on Google в качестве справки, чтобы показать вам, как настроить push-уведомления для вашего действия. Когда пользователи вызывают это действие, оно спрашивает, хотят ли они услышать совет по разработке собственного действия. Пользователи могут выбрать определенную или случайную категорию советов, а также послушать самый последний совет.

Поддерживаемые платформы

Push-уведомления доступны на устройствах Android и iOS (на устройствах iOS должно быть установлено приложение "Google Ассистент"). В настоящее время они не поддерживаются на колонках с голосовым управлением, умных дисплеях и других устройствах.

Требования

По крайней мере одно из действий в вашем проекте должно быть настроено как триггерное намерение, которое будет вызываться, когда пользователь нажимает на уведомление, полученное от Ассистента.

Действия нельзя настроить так, чтобы они запускали стандартное приветственное намерение из push-уведомления.

Настройка консоли

Чтобы добавить поддержку push-уведомлений в действие, выполните следующие действия:

  1. Откройте консоль Actions и выберите Build (Создать) > Actions (Действия).

  2. Нажмите на действие, которое соответствует дополнительному намерению, для которого вы хотите включить push-уведомления.

    В примере с советами от Google по Actions on Google выберите tell_latest_tip.

  3. Прокрутите страницу вниз до раздела Взаимодействие с пользователями и включите параметр Хотите отправлять push-уведомления?

  4. Укажите название контента.

    Для примера с советами Actions on Google заголовок может быть таким: "Добавлен новый совет".

  5. Нажмите Сохранить.

Импорт

Для целей следующих разделов в коде выполнения вам необходимо объявить следующие импорты:

Dialogflow
const {
  dialogflow,
  UpdatePermission,
  Suggestions,
} = require('actions-on-google');
Actions SDK
const {
  actionssdk,
  UpdatePermission,
  Suggestions,
} = require('actions-on-google');

Пользователи, давшие согласие

Прежде чем отправлять пользователям push-уведомления, необходимо получить их согласие. Для этого можно показать пользователю чип с запросом разрешения. Если пользователь предоставит разрешение, вы получите обновленный идентификатор пользователя, чтобы отправлять ему push-уведомления.

Показывать варианты запросов/действий для включения

Чтобы пользователи могли получать push-уведомления от вашего действия, вы должны показать им чип с предложением подписаться на push-уведомления.

Приведенный ниже фрагмент кода отправляет пользователю вариант запроса "Сообщать мне о новых советах" вместе с текстовым ответом.

Dialogflow Node.js
conv.ask('I can send you push notifications. Would you like that?');
conv.ask(new Suggestions('Send notifications'));
Actions SDK Node.js
conv.ask(' I can send you push notifications. Would you like that?');
conv.ask(new Suggestions('Send notifications'));
Dialogflow Java
responseBuilder
    .add("I can send you push notifications. Would you like that?")
    .addSuggestions(new String[] {
        "Send notifications"
    });
Actions SDK Java
responseBuilder
    .add("I can send you push notifications. Would you like that?")
    .addSuggestions(new String[] {
        "Send notifications"
    });
Dialogflow JSON

Обратите внимание, что приведенный ниже код JSON описывает ответ вебхука.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "Hi! Welcome to Push Notifications!"
            }
          },
          {
            "simpleResponse": {
              "textToSpeech": "I can send you push notifications. Would you like that?"
            }
          }
        ],
        "suggestions": [
          {
            "title": "Send notifications"
          }
        ]
      }
    }
  }
}
Actions SDK JSON

Обратите внимание, что приведенный ниже код JSON описывает ответ вебхука.

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TEXT"
        }
      ],
      "inputPrompt": {
        "richInitialPrompt": {
          "items": [
            {
              "simpleResponse": {
                "textToSpeech": "Hi! Welcome to Push Notifications!"
              }
            },
            {
              "simpleResponse": {
                "textToSpeech": " I can send you push notifications. Would you like that?"
              }
            }
          ],
          "suggestions": [
            {
              "title": "Send notifications"
            }
          ]
        }
      }
    }
  ]
}

После того как пользователь нажмет на чип, вы должны запросить разрешение UPDATE. В примере кода ниже показано, как это сделать с помощью функции askForUpdatePermission клиентской библиотеки Node.js.

Dialogflow Node.js
  1. Откройте агент в консоли Dialogflow и выберите намерение, которое вы настраиваете для обновлений.
  2. Прокрутите страницу вниз до раздела Response (Ответ) и откройте вкладку Google Assistant (Google Ассистент).
  3. Нажмите Добавить контент сообщения и выберите Быстрые ответы.
  4. Текст на чипе должен приглашать пользователя дать согласие. В примере с советами Actions on Google мы задали чип Сообщать о новых советах от Google.
  5. Добавьте ещё одно намерение Dialogflow, например setup_push, и задайте соответствующее действие, например setup.push. Выражение намерения пользователя должно соответствовать тексту на чипе согласия, в нашем примере – Сообщать мне о новых советах.
В следующем фрагменте кода показано, как запросить разрешение с помощью клиентской библиотеки Actions on Google для Node.js:
app.intent('Subscribe to Notifications', (conv) => {
  conv.ask(new UpdatePermission({
    intent: 'Notification',
  }));
});
Actions SDK Node.js

Вам нужно настроить решение NLU так, чтобы оно запускало функцию, которая запрашивает разрешение, если выражение пользователя соответствует значению запроса на согласие на получение push-уведомлений. Вот простой пример, основанный на сопоставлении строк:

conv.ask(new UpdatePermission({
  intent: 'Notification',
}));
Dialogflow Java
  1. Откройте агент в консоли Dialogflow и выберите намерение, которое вы настраиваете для обновлений.
  2. Прокрутите страницу вниз до раздела Response (Ответ) и откройте вкладку Google Assistant (Google Ассистент).
  3. Нажмите Добавить контент сообщения и выберите Быстрые ответы.
  4. Текст на чипе должен приглашать пользователя дать согласие. В примере с советами Actions on Google мы задали чип Сообщать о новых советах от Google.
  5. Добавьте ещё одно намерение Dialogflow, например setup_push, и задайте соответствующее действие, например setup.push. Выражение намерения пользователя должно соответствовать тексту на чипе согласия, в нашем примере – Сообщать мне о новых советах.
В следующем фрагменте кода показано, как запросить разрешение с помощью клиентской библиотеки Actions on Google для Java/Kotlin:
@ForIntent("Subscribe to Notifications")
public ActionResponse subscribeToNotifications(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  responseBuilder.add(new UpdatePermission().setIntent("Notification"));
  return responseBuilder.build();
}
Actions SDK Java

Вам нужно настроить решение NLU так, чтобы оно запускало функцию, которая запрашивает разрешение, если выражение пользователя соответствует значению запроса на согласие на получение push-уведомлений. Вот простой пример, основанный на сопоставлении строк:

ResponseBuilder responseBuilder = getResponseBuilder(request);
responseBuilder.add(new UpdatePermission().setIntent("Notification"));
return responseBuilder.build();
Dialogflow JSON

Обратите внимание, что в приведенном ниже коде JSON описан ответ вебхука с использованием Dialogflow.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.PERMISSION",
        "data": {
          "@type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
          "permissions": [
            "UPDATE"
          ],
          "updatePermissionValueSpec": {
            "intent": "tell_latest_tip"
          }
        }
      }
    }
  }
}
Actions SDK JSON

Обратите внимание, что приведенный ниже код JSON описывает ответ вебхука, использующего Actions SDK.

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.PERMISSION",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
            "permissions": [
              "UPDATE"
            ],
            "updatePermissionValueSpec": {
              "intent": "tell_latest_tip"
            }
          }
        }
      ]
    }
  ]
}

Завершите оформление подписки

Чтобы завершить подписку из вебхука Node.js, вам нужно сохранить идентификатор уведомлений пользователя и выбранное им намерение. Если пользователь предоставит разрешение, оба значения будут переданы в качестве аргументов.

Если ваше действие создано с помощью Dialogflow, выполните следующие действия:

  • Добавьте намерение, которое обрабатывает actions_intent_PERMISSION.
  • Укажите название действия, которое вебхук сможет отфильтровать.

В следующем примере показано, как обработать намерение Dialogflow с намерением finish_push_setup и названием действия finish.push.setup:

Dialogflow Node.js
app.intent('Confirm Notifications Subscription', (conv) => {
  if (conv.arguments.get('PERMISSION')) {
    const updatesUserId = conv.arguments.get('UPDATES_USER_ID');
    // Store user ID in database for later use
    conv.close(`Ok, I'll start alerting you.`);
  } else {
    conv.close(`Ok, I won't alert you.`);
  }
});
Actions SDK Node.js
app.intent('actions.intent.PERMISSION', (conv) => {
  if (conv.arguments.get('PERMISSION')) {
    const updatesUserId = conv.arguments.get('UPDATES_USER_ID');
    // Store user ID in database for later use
    conv.close(`Ok, I'll start alerting you.`);
  } else {
    conv.close(`Ok, I won't alert you.`);
  }
});
Dialogflow Java
@ForIntent("Confirm Notifications Subscription")
public ActionResponse confirmNotificationsSubscription(ActionRequest request) {
  // Verify the user has subscribed for push notifications
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  if (request.isPermissionGranted()) {
    Argument userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
    if (userId != null) {
      // Store the user's ID in the database
    }
    responseBuilder.add("Ok, I'll start alerting you.");
  } else {
    responseBuilder.add("Ok, I won't alert you.");
  }
  responseBuilder.endConversation();
  return responseBuilder.build();
}
Actions SDK Java
@ForIntent("actions.intent.PERMISSION")
public ActionResponse confirmNotificationsSubscription(ActionRequest request) {
  // Verify the user has subscribed for push notifications
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  if (request.isPermissionGranted()) {
    Argument userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
    if (userId != null) {
      // Store the user's ID in the database
    }
    responseBuilder.add("Ok, I'll start alerting you.");
  } else {
    responseBuilder.add("Ok, I won't alert you.");
  }
  responseBuilder.endConversation();
  return responseBuilder.build();
}
Dialogflow JSON

Обратите внимание, что в приведенном ниже коде JSON описан запрос к вебхуку.

{
  "responseId": "ee9e7ed5-fa1a-48c6-aac7-f9fbe94f1f58-712767ed",
  "queryResult": {
    "queryText": "actions_intent_PERMISSION",
    "action": "confirm.subscription",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            ""
          ]
        }
      }
    ],
    "outputContexts": [
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_capability_screen_output"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_capability_account_linking"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_capability_media_response_audio"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_capability_audio_output"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_capability_web_browser"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/google_assistant_input_type_keyboard"
      },
      {
        "name": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k/contexts/actions_intent_permission",
        "parameters": {
          "PERMISSION": true,
          "text": "yes",
          "UPDATES_USER_ID": "ABwppHHssyPbvEBF1mgN7Ddwb7mkhiVohW9PZ--I_svqy7zFElA4DHkf9pn04UBd5gwZo26_RfXCQ8otcztyIfe6MCQ"
        }
      }
    ],
    "intent": {
      "name": "projects/PROJECT_ID/agent/intents/c7f7b30b-5b88-4bb5-b0b8-1cd0862d1dd2",
      "displayName": "Confirm Notifications Subscription"
    },
    "intentDetectionConfidence": 1,
    "languageCode": "en"
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "user": {
        "permissions": [
          "UPDATE"
        ],
        "locale": "en-US",
        "userVerificationStatus": "VERIFIED"
      },
      "conversation": {
        "conversationId": "ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k",
        "type": "ACTIVE",
        "conversationToken": "[]"
      },
      "inputs": [
        {
          "intent": "actions.intent.PERMISSION",
          "rawInputs": [
            {
              "inputType": "KEYBOARD",
              "query": "yes"
            }
          ],
          "arguments": [
            {
              "name": "PERMISSION",
              "boolValue": true,
              "textValue": "true"
            },
            {
              "name": "text",
              "rawText": "yes",
              "textValue": "yes"
            },
            {
              "name": "UPDATES_USER_ID",
              "textValue": "ABwppHHssyPbvEBF1mgN7Ddwb7mkhiVohW9PZ--I_svqy7zFElA4DHkf9pn04UBd5gwZo26_RfXCQ8otcztyIfe6MCQ"
            }
          ]
        }
      ],
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.ACCOUNT_LINKING"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": "projects/PROJECT_ID/agent/sessions/ABwppHGIgmmU3zBcYMF_vWoHaM4JUo3wniYBHdbUF25l63G7EQWjRnlne8Ar7AOcRHWn1lrEKGy8qdP0UXLcWDBq93k"
}
Actions SDK JSON

Обратите внимание, что в приведенном ниже коде JSON описан запрос к вебхуку.

{
  "user": {
    "permissions": [
      "UPDATE"
    ],
    "locale": "en-US",
    "userVerificationStatus": "VERIFIED"
  },
  "conversation": {
    "conversationId": "ABwppHEP6OAFZHkSGEiZ5HYM9qrlk8YtIH1DQmJ52cxXELSPvM-kSc_tMJ_5O6ITbgVJlY9i2FIsKWjE_HXLke48",
    "type": "NEW"
  },
  "inputs": [
    {
      "intent": "actions.intent.PERMISSION",
      "rawInputs": [
        {
          "inputType": "KEYBOARD",
          "query": "yes"
        }
      ],
      "arguments": [
        {
          "name": "PERMISSION",
          "boolValue": true,
          "textValue": "true"
        },
        {
          "name": "text",
          "rawText": "yes",
          "textValue": "yes"
        },
        {
          "name": "UPDATES_USER_ID",
          "textValue": "ABwppHFvBKC-tMYUsUjJkm3YECgZvd6A3sOc7KuQvO4ZdQX3bGLmyoQ41dh4Zmtlzv_kaOKBt1Sf6eRpNbayynrl"
        }
      ]
    }
  ],
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      },
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.ACCOUNT_LINKING"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      }
    ]
  },
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}

Отправка уведомлений

Вы можете отправлять пользователям push-уведомления с помощью Actions API. Чтобы использовать этот API, активируйте его в проекте Google Cloud, а также настройте и скачайте ключ сервисного аккаунта JSON. См. шаг 8 в инструкциях к примеру кода.

Затем вы можете использовать клиентскую библиотеку Google OAuth2, чтобы обменять ключ сервисного аккаунта на токен доступа и использовать его для аутентификации запросов к Actions API.

Как получить ключ для сервисного аккаунта

  1. Перейдите по этому URL, заменив "example-project-1" в конце на идентификатор вашего проекта в консоли Actions: https://console.developers.google.com/apis/api/actions.googleapis.com/overview?project=example-project-1
  2. Если вы видите кнопку Включить, нажмите ее. В противном случае перейдите к шагу 3.
  3. Перейдите по этому URL, заменив example-project-1 в конце на идентификатор проекта в консоли Actions: https://console.developers.google.com/apis/credentials?project=example-project-1
  4. Нажмите Create credentials (Создать учетные данные) > Service Account Key (Ключ сервисного аккаунта).
  5. Нажмите на поле Выбрать в разделе Сервисный аккаунт и выберите Новый сервисный аккаунт.
  6. Укажите название сервисного аккаунта, например "Уведомления", и выберите роль Владелец проекта.
  7. Выберите тип ключа JSON и нажмите Create (Создать). Ключ сервисного аккаунта в формате JSON будет скачан на ваш компьютер.

Обменяйте ключ на токен доступа и отправьте уведомление

Чтобы отправить уведомление через Actions API, вам нужно обменять ключ сервисного аккаунта на токен доступа. Для этого рекомендуется использовать клиентскую библиотеку Google API. В приведенных ниже фрагментах кода используется клиентская библиотека API Google Node.js.

  1. Установите клиентскую библиотеку API Google и отправьте запрос: npm install googleapis request --save
  2. Используйте следующий код, чтобы получить токен доступа из ключа сервисного аккаунта и отправить push-уведомление:
Dialogflow Node.js
const {google} = require('googleapis');
const request = require('request');

const jwtClient = new google.auth.JWT(
  serviceAccount.client_email, null, serviceAccount.private_key,
  ['https://www.googleapis.com/auth/actions.fulfillment.conversation'],
  null
);

jwtClient.authorize((err, tokens) => {
  if (!err) {
    request.post('https://actions.googleapis.com/v2/conversations:send', {
      auth: {
        bearer: tokens.access_token,
      },
      json: true,
      body: {
        customPushMessage: {
          userNotification: {
            title: 'Push Notification Title',
          },
          target: {
            userId: '<UPDATES_USER_ID>',
            intent: 'Notification Intent',
          },
        },
        isInSandbox: true,
      },
    }, (err, httpResponse, body) => {
      console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
    });
  }
});
Actions SDK Node.js
const {google} = require('googleapis');
const request = require('request');

const jwtClient = new google.auth.JWT(
  serviceAccount.client_email, null, serviceAccount.private_key,
  ['https://www.googleapis.com/auth/actions.fulfillment.conversation'],
  null
);

jwtClient.authorize((err, tokens) => {
  if (!err) {
    request.post('https://actions.googleapis.com/v2/conversations:send', {
      auth: {
        bearer: tokens.access_token,
      },
      json: true,
      body: {
        customPushMessage: {
          userNotification: {
            title: 'Push Notification Title',
          },
          target: {
            userId: '<UPDATES_ORDER_ID>',
            intent: 'Notification Intent',
          },
        },
        isInSandbox: true,
      },
    }, (err, httpResponse, body) => {
      console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
    });
  }
});
Dialogflow Java
final class Notification {

  private final String title;

  Notification(String title) {
    this.title = title;
  }

  String getTitle() {
    return title;
  }
}

final class Target {

  private final String userId;
  private final String intent;
  private final String locale;

  Target(String userId, String intent, String locale) {
    this.userId = userId;
    this.intent = intent;
    this.locale = locale;
  }

  String getUserId() {
    return userId;
  }

  String getIntent() {
    return intent;
  }

  String getLocale() {
    return locale;
  }
}

final class PushMessage {

  private final Notification userNotification;
  private final Target target;

  PushMessage(Notification userNotification, Target target) {
    this.userNotification = userNotification;
    this.target = target;
  }

  Notification getUserNotification() {
    return userNotification;
  }

  Target getTarget() {
    return target;
  }
}

final class PushNotification {

  private final PushMessage customPushMessage;
  private boolean isInSandbox;

  PushNotification(PushMessage customPushMessage, boolean isInSandbox) {
    this.customPushMessage = customPushMessage;
    this.isInSandbox = isInSandbox;
  }

  PushMessage getCustomPushMessage() {
    return customPushMessage;
  }

  boolean getIsInSandbox() {
    return isInSandbox;
  }
}

private PushNotification createNotification(String title, String userId, String intent, String locale) {
  Notification notification = new Notification(title);
  Target target = new Target(userId, intent, locale);
  PushMessage message = new PushMessage(notification, target);
  boolean isInSandbox = true;
  return new PushNotification(message, isInSandbox);
}

private ServiceAccountCredentials loadCredentials() throws IOException {
  String actionsApiServiceAccountFile =
      this.getClass().getClassLoader().getResource("service-account.json").getFile();
  InputStream actionsApiServiceAccount = new FileInputStream(actionsApiServiceAccountFile);
  ServiceAccountCredentials serviceAccountCredentials =
      ServiceAccountCredentials.fromStream(actionsApiServiceAccount);
  return (ServiceAccountCredentials)
      serviceAccountCredentials.createScoped(
          Collections.singleton(
              "https://www.googleapis.com/auth/actions.fulfillment.conversation"));
}

private String getAccessToken() throws IOException {
  AccessToken token = loadCredentials().refreshAccessToken();
  return token.getTokenValue();
}

public void sendNotification(String title, String userId, String intent, String locale) throws IOException {
  Preconditions.checkNotNull(title, "title cannot be null.");
  Preconditions.checkNotNull(userId, "userId cannot be null.");
  Preconditions.checkNotNull(intent, "intent cannot be null.");
  Preconditions.checkNotNull(locale, "locale cannot be null");
  PushNotification notification = createNotification(title, userId, intent, locale);

  HttpPost request = new HttpPost("https://actions.googleapis.com/v2/conversations:send");

  String token = getAccessToken();

  request.setHeader("Content-type", "application/json");
  request.setHeader("Authorization", "Bearer " + token);

  StringEntity entity = new StringEntity(new Gson().toJson(notification));
  entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
  request.setEntity(entity);
  HttpClient httpClient = HttpClientBuilder.create().build();
  httpClient.execute(request);
}
Actions SDK Java
final class Notification {

  private final String title;

  Notification(String title) {
    this.title = title;
  }

  String getTitle() {
    return title;
  }
}

final class Target {

  private final String userId;
  private final String intent;

  Target(String userId, String intent) {
    this.userId = userId;
    this.intent = intent;
  }

  String getUserId() {
    return userId;
  }

  String getIntent() {
    return intent;
  }
}

final class PushMessage {

  private final Notification userNotification;
  private final Target target;

  PushMessage(Notification userNotification, Target target) {
    this.userNotification = userNotification;
    this.target = target;
  }

  Notification getUserNotification() {
    return userNotification;
  }

  Target getTarget() {
    return target;
  }
}

final class PushNotification {

  private final PushMessage customPushMessage;
  private boolean isInSandbox;

  PushNotification(PushMessage customPushMessage, boolean isInSandbox) {
    this.customPushMessage = customPushMessage;
    this.isInSandbox = isInSandbox;
  }

  PushMessage getCustomPushMessage() {
    return customPushMessage;
  }

  boolean getIsInSandbox() {
    return isInSandbox;
  }
}

private PushNotification createNotification(String title, String userId, String intent) {
  Notification notification = new Notification(title);
  Target target = new Target(userId, intent);
  PushMessage message = new PushMessage(notification, target);
  boolean isInSandbox = true;
  return new PushNotification(message, isInSandbox);
}

private ServiceAccountCredentials loadCredentials() throws IOException {
  String actionsApiServiceAccountFile =
      this.getClass().getClassLoader().getResource("service-account.json").getFile();
  InputStream actionsApiServiceAccount = new FileInputStream(actionsApiServiceAccountFile);
  ServiceAccountCredentials serviceAccountCredentials =
      ServiceAccountCredentials.fromStream(actionsApiServiceAccount);
  return (ServiceAccountCredentials)
      serviceAccountCredentials.createScoped(
          Collections.singleton(
              "https://www.googleapis.com/auth/actions.fulfillment.conversation"));
}

private String getAccessToken() throws IOException {
  AccessToken token = loadCredentials().refreshAccessToken();
  return token.getTokenValue();
}

public void sendNotification(String title, String userId, String intent) throws IOException {
  Preconditions.checkNotNull(title, "title cannot be null.");
  Preconditions.checkNotNull(userId, "userId cannot be null.");
  Preconditions.checkNotNull(intent, "intent cannot be null.");
  PushNotification notification = createNotification(title, userId, intent);

  HttpPost request = new HttpPost("https://actions.googleapis.com/v2/conversations:send");

  String token = getAccessToken();

  request.setHeader("Content-type", "application/json");
  request.setHeader("Authorization", "Bearer " + token);

  StringEntity entity = new StringEntity(new Gson().toJson(notification));
  entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
  request.setEntity(entity);
  HttpClient httpClient = HttpClientBuilder.create().build();
  httpClient.execute(request);
}