概要
ApexでAccount Engagementの行動履歴をAPIで取得する処理を作成したときの備忘です。
API実行に必要なアクセストークンを取得する認証APIとAccount Engagementの行動履歴を取得するAPIの2コを利用します。
認証APIにはいくつか方式がありますが、ユーザー名パスワードフローを使用しました。ユーザ名とパスワードを送信するため、非推奨とされていますが、クライアントログイン情報フローを使うとコールバックURLにリダイレクトしてしまうため、おなじApex内で処理が続行できませんでした。ユーザー名パスワードフローはリダイレクトしなかったのですが、クライアントログイン情報フローでリダイレクトしないやり方はないのですかね。ご存じの方、いらっしゃいましたら、教えてください。
2024年8月時点の情報になります。
Account Engagementの行動履歴とは
プロスペクトがメールを受信、開封したり、ランディングページにアクセスしたりといったプロスペクトのアクティビティに表示される情報です。この情報はSales Cloudからオブジェクトとして参照できないので、APIで取得します。
設定
アプリケーションマネージャー
「アプリケーションマネージャー」で「新規接続アプリケーション」を作成します。
「OAuth 設定の有効」のチェックを「オン」に変更し、「利用可能な OAuth 範囲」を選択します。今回は、Account Engagement APIを利用したいので、「Pardot サービスを管理 (pardot_api)」を選択します。
接続アプリケーションを作成すると「コンシューマー鍵」と「コンシューマーの秘密」が発行されます。認証APIで必要になるので、覚えておきます。
接続元のIPアドレスの制限として「IP制限の緩和」という項目があり、値を「IP制限の緩和」としました。今回はApexから呼び出すので、厳格にやる場合は、「IP制限を適用」とし、あとで作成する接続用ユーザのプロファイルのIPアドレス制限にSalesforceのIPアドレスをすべて設定します。
リモートサイトの設定
「リモートサイトの設定」でSalesforceから外部接続を許可するURLを追加します。
※本番環境とテスト環境でURLが異なるので注意してください
production
- 認証API用:https://login.salesforce.com
- Account Engagement API用:https://pi.pardot.com
sandbox
- 認証API用:https://test.salesforce.com
- Account Engagement API用:https://pi.demo.pardot.com
OAuth および OpenID Connect 設定
「OAuth および OpenID Connect 設定」で「OAuthユーザー名パスワードフローを許可」を「オン」に変更します。
ユーザー
「ユーザー」でAPI実行用ユーザーを作成します。ユーザーライセンスは「Identity」プロファイルは「Identity User」を選択します。
ユーザーを作成したら、権限セットの割り当てで「Account Engagement Integration User」を追加します。Account Engagementの情報を参照するのに必要な権限になります。
認証API
SalesforceのAPIを実行するためのアクセストークンを取得するAPI
マニュアル
特別なシナリオの OAuth 2.0 ユーザー名パスワードフロー
リクエスト
エンドポイント
環境 | エンドポイント | メソッド |
---|---|---|
production | https://login.salesforce.com/services/oauth2/token | POST |
sandbox | https://test.salesforce.com/services/oauth2/token | POST |
パラメータ
キー | 値 |
---|---|
grant_type | password |
client_id | 接続アプリケーションで発行した「コンシューマー鍵」 |
client_secret | 接続アプリケーションで発行した「コンシューマーの秘密」 |
username | API実行用に作成したユーザの「ユーザ名」 |
password | API実行用に作成したユーザの「パスワード」 |
ヘッダー
不要
レスポンス
キー | 値 |
---|---|
access_token | APIで利用するアクセストークン |
instance_url | 使わない |
id | 使わない |
token_type | 使わない |
issued_at | 使わない |
signature | 使わない |
Account Engagement Visitor Activity API
Account Engagementの行動履歴を取得するAPI
マニュアル
リクエスト
エンドポイント
環境 | エンドポイント | メソッド |
---|---|---|
production | https://pi.pardot.com/api/v5/objects/visitor-activities | GET |
sandbox | https://pi.demo.pardot.com/api/v5/objects/visitor-activities | GET |
パラメータ(詳細はマニュアルをみてください)
キー | 値 |
---|---|
fields | 取得する項目をカンマ区切りで列挙(マニュアルをみてください) |
limit | データの取得件数(デフォルト200で、1,000まで指定可) |
ソート項目 | ソート条件(マニュアルをみてください) |
フィルタ項目 | フィルタ条件(マニュアルをみてください) |
ヘッダー
キー | 値 |
---|---|
Authorization | Bearer 認証APIで取得したアクセストークン |
Pardot-Business-Unit-Id | ビジネスユニットID |
※Authorizationの値のBearerのあとは半角スペースがはいります。
※ビジネスユニットIDは「ビジネスユニット設定」で確認してください。
レスポンス
キー | 値 |
---|---|
nextPageToken | 次のページアクセストークン(なければnull) |
nextPageUrl | 次のページのURL(なければnull) |
values | 取得した値 |
※1回のリクエストで最大1,000件までのデータしか取得できません。すべてのデータが取得できない場合、「nextPageToken」と「nextPageUrl」に値が設定され、「nextPageUrl」をたどっていくことですべてのデータを取得することができます。「nextPageUrl」にリクエストする際、「nextPageToken」でなく、最初に認証APIで取得したアクセストークンでも動作しました。
Apex
実装
API呼び出しのサンプルプログラムです。
- フローからApexアクションで呼び出すのにInvocableMethodアノテーションを使用しています
- APIを呼び出すには、futureアノテーションを使用しないと、CalloutExceptionが発生します
- データを複数件取得する場合、レスポンスの「nextPageUrl」を判定して、nullになるまで、apiをよぶ必要があります(サンプルプログラムではやっていません)
- APIのエンドポイントなど、環境依存の定数はカスタム表示ラベルを使用しています
public class ApiSample {
// 定数
// 認証API URL
static final String AUTHENTICATION_API_URL = System.Label.Authentication_Api_Url;
// VISITOR ACTIVITY API URL
static final String VISITOR_ACTIVITY_API_URL = System.Label.Visitor_Activity_Api_Url;
// コンシューマ鍵
static final String CLIENT_ID = System.Label.Client_Id;
// コンシューマ秘密
static final String CLIENT_SECRET = System.Label.Client_Secret;
// ユーザ名
static final String USERNAME = System.Label.Username;
// ユーザパスワード
static final String PASSWORD = System.Label.Password;
// ビジネスユニットID
static final String BUSINESS_UNIT_ID = System.Label.Business_Unit_Id;
/**
* フローから呼び出されるメソッド
* @param batchParams パラメータ
* @return なし
*
**/
@InvocableMethod(label='Run Batch Job')
public static void runBatchJob(List<String> batchParams) {
main();
}
/**
* メイン処理
*
* @return なし
*
**/
@future(callout=true)
public static void main() {
// 認証APIをよぶ
String accessToken = apiAuthentication();
// visitor activity apiをよぶ
apiVisitorActivity(accessToken);
}
/**
* visitor activity apiを実行する
*
* @param accessToken アクセストークン
* @return apiレスポンスのbody
*
**/
private static String apiVisitorActivity(String accessToken) {
// httpリクエストとレスポンスの初期化
HttpRequest req = new HttpRequest();
HttpResponse res = null;
Http http = new Http();
// getパラメータの設定
String param = '?fields=prospect.email';
param += '&type=6';
param += '&limit=1000';
param += '&orderby=createdAt';
// リクエストの設定
req.setEndpoint(VISITOR_ACTIVITY_API_URL + param);
req.setMethod('GET');
// リクエストヘッダの設定
req.setHeader('Authorization', 'Bearer ' + accessToken);
req.setHeader('Pardot-Business-Unit-Id', BUSINESS_UNIT_ID);
try {
// httpリクエスト送信
res = http.send(req);
// ステータスコードのチェック
if (res.getStatusCode() != 200) {
System.debug('api call failure:' + res.getBody());
return null;
}
} catch (Exception e) {
System.debug('Exception:' + e.getMessage());
}
return res.getBody();
}
/**
* 認証apiを実行する
*
* @param accessToken アクセストークン
* @return apiレスポンスのbody
*
**/
private static String apiAuthentication() {
// httpリクエストとレスポンスの初期化
HttpRequest req = new HttpRequest();
HttpResponse res = null;
Http http = new Http();
// リクエストの設定
req.setEndpoint(AUTHENTICATION_API_URL);
req.setMethod('POST');
// リクエストヘッダの設定
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
// リクエストボディの設定
String body = 'grant_type=' + EncodingUtil.urlDecode('password', 'utf-8');
body += '&client_id=' + EncodingUtil.urlDecode(CLIENT_ID, 'utf-8');
body += '&client_secret=' + EncodingUtil.urlDecode(CLIENT_SECRET, 'utf-8');
body += '&username=' + EncodingUtil.urlDecode(USERNAME, 'utf-8');
body += '&password=' + EncodingUtil.urlDecode(PASSWORD, 'utf-8');
req.setBody(body);
try {
// httpリクエスト送信
res = http.send(req);
// ステータスコードのチェック
if (res.getStatusCode() != 200) {
System.debug('api call failure:' + res.getBody());
return null;
}
} catch (Exception e) {
System.debug('Exception:' + e.getMessage());
}
// アクセストークンを返却
Map<String, Object> jsonResponse = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
return (String)jsonResponse.get('access_token');
}
}
テストクラス
テストクラスのサンプルプログラムです。
- API呼び出しはモックを使用します
- HttpResponseのレスポンス処理がすべて同一のモック処理になってしまうので、リクエストURLを判定することで、それぞれのリクエストに応じたレスポンスを返すようにしています
@isTest
private class ApiSampleTest {
@isTest
private static void test1() {
Test.setMock(HttpCalloutMock.class, new CalloutMock());
Test.startTest();
ApiSample.runBatchJob(null);
Test.stopTest();
}
// apiモック
private class CalloutMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
// 認証apiのレスポンス
if (req.getEndpoint().contains(System.Label.Authentication_Api_Url)) {
res.setStatusCode(200);
res.setBody('{"access_token": "xxx","instance_url": "xxx","id": "xxx","token_type": "xxx","issued_at": "xxx","signature": "xxx"}');
}
// visitor activity api1回目のレスポンス
if (req.getEndpoint().contains(System.Label.Visitor_Activity_Api_Url)) {
res.setStatusCode(200);
res.setBody('{"nextPageToken": "","nextPageUrl": "https://nextpageurl","values":[]}');
}
// visitor activity api2回目のレスポンス
if (req.getEndpoint().contains('https://nextpageurl')) {
res.setStatusCode(200);
res.setBody('{"nextPageToken": null,"nextPageUrl": null,"values": []}');
}
return res;
}
}
}
エラー調査
認証がうまくいかない場合、「設定」「ユーザー」から、API実行用ユーザのログイン履歴をみると、失敗した原因が確認できるので、参考になりました。