実装で学ぶ Adobe Campaign Standard API – 実装編 #AdobeIO

Adobe I/O Corporate

概要

実装で学ぶ Adobe Campaign Standard API – 開発の準備編で開発の技術要件や開発環境を整えたところで、今回はいよいよ実装に入りましょう。

まず、開発の目的として、なめこ株式会社の以下の課題を解決するものとします。

  1. 一貫性のあるブランド体験を提供するために企業サイトでキャンペーンページを配置し、サービスの opt-in / opt-out をできるようにする
  2. 更新された会員情報をいち早く Adobe Campaign Standard(以下 ACS)にマージし、スピード感のあるリワードキャンペーンを行う
  3. 買い物かご放棄を検出時に通知メールを送信し、コンバージョンを高める

これらの課題を解決するために ACS では以下の API が用意されています。リンク先は該当 API のドキュメント URL ですが、本記事ではソースコードのコメントでドキュメント URL を併記しております。

  1. クーポンキャンペーンを用いたユーザー追加・更新・サービスの購読
  2. ACS 上の会員データ同期ワークフローをトリガー
  3. 買い物かご放棄を検出時に通知メールを送信

ユースケースの全体像は以下のようなイメージになります。ACS インスタンスの設定と PHP のコードサンプルを交えて説明していきます。

API 資格情報セキュリティの基本

サービス全体のアーキテクチャやユースケースによって要求されるセキュリティが変わると思いますが、API の資格情報(credentials)を安全に利用するためにこれらの基準を考慮してください。

  • アプリケーションのソースコードにハードコードしない
  • 資格情報を Hashicorp Vault などの機密情報データベースに保存するか、資格情報をローカルシステムに置くのであれば公開ディレクトリ配下に置かない
  • JWT はセントラルサービスサーバーからアプリケーションサーバーにプッシュし、アプリケーションサーバーが JWT のパブリックキーを使って JWT の真正性を検証してから受け入れる
  • JWT の有効期限を短くする
  • アプリケーションサーバーが危険にさらされた事態に備えて、Back-channel logout API を使ってアクセストークンを無効化にする

Back-channel logout API によるアクセストークンの無効化

本記事のサンプルコードではカバーしませんが、アプリケーションが危険にさらされるなど不測の事態が起きた場合、Back-channel logout API を使ってアクセストークンを失効させる事ができます。

GET /ims/logout/v1?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&access_token=ACCESS_TOKEN HTTP/1.1
Host: ims-na1.adobelogin.com

  • YOUR_CLIENT_ID:API Key (Client ID)、Adobe I/O コンソールから確認できます
  • YOUR_CLIENT_SECRET:Client secret、Adobe I/O コンソールから確認できます
  • ACCESS_TOKEN:JWT で交換したアクセストークン

このリクエストの期待されるレスポンスは下記の通りです。

HTTP/1.1 200
Cache-Control: no-store

Back-channel logout API リクエストを行うと、おおよそ 60 秒後にアクセストークンが無効になります。

サンプルコードの実行環境

本記事で紹介する PHP のサンプルコードは、以下の環境でテストしています。

  • Apache 2.4.34
  • PHP 7.1.23

また、サンプルコードで以下のライブラリを使用しています。ライブラリの依存関係は composer を使用しています。

  • PHP-JWT: JWT のエンコード / デコード
  • httpful: 軽量な http クライアント

composer、httpful、PHP-JWT のインストール方法についてはそれぞれのドキュメントを参照してください。

アクセストークンの取得と更新

Adobe I/O の ACS API 通信を行うために以下の資格情報が必要です。

  • JWT(JSON Web Token)
  • クライアントの資格情報
    • Client ID
    • Client secret
  • Access token

JWT の署名、エンコード、デコード

JWT の取得については、前編でも触れましたが Adobe I/O コンソールから手動で取得できます。プログラムを書いて一から JWT を作成する事もできますが、JWT を生成するライブラリがありますので、ここでは PHP-JWT で JWT を作成する方法を紹介します。なお、他のプログラミング言語で JWT を生成する場合、Adobe I/O の技術ドキュメント Creating a JSON Web Token を参考にしてください。

また、セキュリティ強化のために JWT の生成はアプリケーションと異なるサーバーで行い、生成される JWT をアプリケーションサーバーに配信(または返答)するデータフローを検討してください。

jwt-encode.php | JWT の署名、エンコード

// Composer のオートロード
require_once 'vendor/autoload.php';
use \Firebase\JWT\JWT;

$privateKeyFile = '/path/to/io-test-2018-11.key'; // プライベートキーは DER 形式、作成方法は前回の記事に書いてあります
//  -----BEGIN PRIVATE KEY-----
//  XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
//  XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
//  -----END PRIVATE KEY-----

$publicKeyFile  = '/path/to/io-test-2018-11_pub.crt'; // パブリックキーを Adobe I/O Console に登録を忘れずに
//  -----BEGIN CERTIFICATE-----
//  XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
//  XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
//  -----END CERTIFICATE-----

$jwtClaimsFile  = '/path/to/jwt-claims.json';
//  {
//    "exp":"",
//    "iss": "XXXXXXXXXXXXXXXXXXXXXXXX@AdobeOrg",
//    "sub": "XXXXXXXXXXXXXXXXXXXXXXXX@techacct.adobe.com",
//    "https://ims-na1.adobelogin.com/s/ent_campaign_sdk": true,
//    "aud": "https://ims-na1.adobelogin.com/c/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
//  }
//
//  Service Account の JWT を作成する上で必要なクレーム | Required Claims for a Service Account JWT
//  https://www.adobe.io/apis/cloudplatform/console/authentication/jwt_workflow.html
//
//    exp: // 作成される JWT の有効期限(Unix epoch 時間)
//    iss: // Organization ID(Adobe I/O コンソールから取得)
//    sub: // Technical account ID(Adobe I/O コンソールから取得)
//    https://ims-na1.adobelogin.com/s/ent_campaign_sdk // 組織向けに設定されたクレーム、固定値 true
//    aud: // トークンのオーディエンス、書式:https://ims-na1.adobelogin.com/c/【

$privateKey = file_get_contents($privateKeyFile);
$publicKey  = file_get_contents($publicKeyFile);
$jwtClaims  = json_decode(file_get_contents($jwtClaimsFile), true);

$jwtClaims['exp'] = strtotime('+1 day'); // 有効期限を1日にする場合

/*
  JWT を署名しエンコードする
*/
$jwt = JWT::encode($jwtClaims, $privateKey, 'RS256');
echo '<h1>Encoded</h1><textarea rows="12" cols="80">' . print_r($jwt, true) . '</textarea>';

jwt-decode.php | JWT のデコード

// Composer のオートロード
require_once 'vendor/autoload.php';
use \Firebase\JWT\JWT;

$publicKey  = file_get_contents($publicKeyFile);
$jwt ='eyJxxxx.xxxx.xxxx';

/* JWT をデコードする
*/
$decoded = JWT::decode($jwt, $publicKey, array('RS256'));

Access token の取得

JWT をアクセストークンと交換するには、次の API を使用します。

サンプルコード:アクセストークンの取得

$url = "https://ims-na1.adobelogin.com/ims/exchange/jwt/";
$response = \Httpful\Request::post($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/x-www-form-urlencoded',
    ))
  ->body('client_id='.$client['id'].'&client_secret='.$client['secret'].'&jwt_token='.$jwt)
  ->send();

$token  = json_decode($response->raw_body);

このリクエストの期待されるレスポンスは下記の通りです。

Response body

HTTP/1.1 200 OK
{
  "token_type": "bearer",
  "access_token": "xxxxxx.xxxxxx.xxxxxx",
  "expires_in": 86399995
}

「expires_in」はアクセストークンの残り有効時間をミリ秒で表しております。実装ではこの期限内に更新するようにしてください。

プロフィールとサービスの操作

サービスの作成(Campaign 側設定)

まずは、ユーザーがメルマガを購読するために、メルマガ購読者の購読状況を保存する場所を指定しますが、ACS ではこうしたサブスクリプションの関連情報を保存するためにサービス(Services)を利用します。

  1. ナビゲーションバーから Profiles へ移動します
  2. Profiles ドロップダウンリストを展開し、Profiles & audiences をクリックします
  3. Services をクリックします
  4. すると既存サービスの一覧が表示されますので、右上の Create をクリックします
  5. 作成する Service の種類を選びます。デフォルトでは Newsletter のテンプレートがありますので、それを選択し右上の次へをクリックします
  6. このサービスの名前と ID を指定しますが、ID は実際の API コールで使われますので、分かりやすく短いものが良いでしょう
  7. Create をクリックしてサービスを作成します

サンプルコード

メルマガ登録

まずは会員のメルマガ購読を登録する処理を紹介します。これを行うには ACS のプロフィールやサービスを操作する API を使います。大まかな流れ:

  1. ACS 上でプロフィール(=会員データ)の存在を確認、存在しなければ作成
  2. 作成されたプロフィールに対しサービスを紐付けする(メルマガの登録)

このユースケースで以下の固有パラメータを使います。

  • TENANT_ID($tenantId):テナント ID。これは2つの場所で確認する事ができます
    • Adobe Experience Cloud ➔ Admin Console ➔ Adobe Campaign Standard から、インスタンス名になっている文字列
    • Campaign にログイン後、アドレスバーに表示されるURLの .campaign.adobe.com の左側のサブドメイン名
  • メルマガの PKey と internal name:internal name は先ほどメルマガサービスを作ったときに決めたものですが、PKey は Campaign の UI 上では確認できないので、購読の一覧を取得する「getsubscriptionurls()」を使用してメルマガの PKey を確認してください。

購読の一覧を取得するには、次の API を使用します。

サンプルコード:購読の一覧を取得する

$url = "https://mc.adobe.io/" . $tenantId . "/campaign/profileAndServices/service";
$response = \Httpful\Request::get($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$subscriptionurls = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。「content[n].PKey」はこのサービスのユニークキーで、後ほどサービスの購読を行うために必要になります。

Response body

HTTP/1.1 200 OK
{
  "content": [
    {
      "PKey": "@xxxxxx",
      "builtIn": false,
      "created": "2018-04-12 09:32:40.875Z",
      "desc": "",
      "end": "",
      "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service/@xxxxx",
      "isExternal": false,
      "isTemplate": false,
      "label": "メルマガ",
      "lastModified": "2018-04-12 09:32:40.875Z",
      "limitedDuration": false,
      "mainDate": "2018-04-12",
      "messageType": "email",
      "mode": "newsletter",
      "name": "mail-mag",
      "publicLabel": "",
      "start": "2018-04-12",
      "subScenarioEventType": "subscriptionEvent",
      "subscriptions": {
        "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service/@xxxxx/subscriptions/"
      },
      "targetResource": "profile",
      "template": {
        "PKey": "@xxxxx",
        "title": "Newsletter (newsletter)"
      },
      "thumbnail": "/nl/img/thumbnails/defaultService.png",
      "title": "メルマガ (mail-mag)",
      "unsubScenarioEventType": "unsubscriptionEvent",
      "validityDuration": "P10D"
    },
    {
      "PKey": "@xxxxx",
      "builtIn": false,
      "created": "2018-11-05 06:34:15.412Z",
      "desc": "",
      "end": "",
      "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service/@xxxxx",
      "isExternal": false,
      "isTemplate": false,
      "label": "ニュースレター",
      "lastModified": "2018-11-05 07:33:09.926Z",
      "limitedDuration": false,
      "mainDate": "2018-11-05",
      "messageType": "email",
      "mode": "newsletter",
      "name": "news-letter",
      "publicLabel": "",
      "start": "2018-11-05",
      "subScenarioEventType": "",
      "subscriptions": {
        "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service/@xxxxx/subscriptions/"
      },
      "targetResource": "profile",
      "template": null,
      "thumbnail": "/nl/img/thumbnails/defaultService.png",
      "title": "ニュースレター (news-letter)",
      "unsubScenarioEventType": "",
      "validityDuration": "P10D"
    }
  ],
  "count": {
    "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service//_count?_lineStart=@xxxxx",
    "value": 2
  },
  "serverSidePagination": true
}

プロフィールを取得するには、次の API を使用します。

サンプルコード:プロフィールの取得

$url = "https://mc.adobe.io/" . $tenantId . "/campaign/profileAndServices/profile/byEmail?email=" . urlencode($email);
$response = \Httpful\Request::get($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$profile = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。

Response body

HTTP/1.1 200 OK
{
  "content": [
    {
      "PKey": "@xxxxxx",
      "age": 0,
      "birthDate": "",
      "blackList": false,
      "blackListEmail": false,
      "blackListFax": false,
      "blackListMobile": false,
      "blackListPhone": false,
      "blackListPostalMail": false,
      "blackListPushnotification": false,
      "created": "2018-11-05 07:52:08.176Z",
      "cryptedId": "xxxxxx",
      "domain": "example.com",
      "email": "user@example.com",
      "emailFormat": "unknown",
      "fax": "",
      "firstName": "Baiz",
      "gender": "unknown",
      "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx",
      "isExternal": false,
      "lastModified": "2018-11-05 07:52:24.250Z",
      "lastName": "Yeh",
      "location": {
        "address1": "",
        "address2": "",
        "address3": "",
        "address4": "",
        "city": "",
        "countryCode": "",
        "stateCode": "",
        "zipCode": ""
      },
      "middleName": "",
      "mobilePhone": "123456789",
      "phone": "",
      "postalAddress": {
        "addrDefined": false,
        "addrErrorCount": 0,
        "addrLastCheck": "",
        "addrQuality": "0",
        "line1": "Baiz YEH",
        "line2": "",
        "line3": "",
        "line4": "",
        "line5": "",
        "line6": "",
        "serialized": "Baiz YEH\n\n\n\n\n"
      },
      "preferredLanguage": "none",
      "salutation": "",
      "subscriptions": {
        "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/"
      },
      "thumbnail": "http://TENANT-ID-res.adobe-campaign.com/res/xxxxxx_mkt_stage1/xxxxxx.jpg",
      "timeZone": "none",
      "title": "Baiz Yeh (user@example.com)"
    }
  ],
  "count": {
    "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile//byEmail/_count?email=user@example.com&_lineStart=@xxxxxx",
    "value": 1
  },
  "serverSidePagination": true
}

プロフィールを作成するには、次の API を使用します。

サンプルコード:プロフィールの作成

$url = "https://mc.adobe.io/" . $tenantId . "/campaign/profileAndServices/profile";
$response = \Httpful\Request::post($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json;charset=utf-8', // 日本語を含むプロフィールを作成する場合 charset の追加は必須
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->body('{"email":"' .$email. '", "firstName":"' .$firsName. '", "lastName":"' .$lastName. '"}')
  ->send();

$profile = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。「subscriptions.href」は、後ほどサービスの購読を行うために必要になります。

Response body

HTTP/1.1 201 Created
{
  "PKey": "@xxxxxx",
  "age": 0,
  "birthDate": "",
  "blackList": false,
  "blackListEmail": false,
  "blackListFax": false,
  "blackListMobile": false,
  "blackListPhone": false,
  "blackListPostalMail": false,
  "blackListPushnotification": false,
  "created": "2018-12-13 07:59:26.434Z",
  "cryptedId": "xxxxxx",
  "domain": "example.com",
  "email": "user@example.com",
  "emailFormat": "unknown",
  "fax": "",
  "firstName": "Baiz",
  "gender": "unknown",
  "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx",
  "isExternal": false,
  "lastModified": "2018-12-13 07:59:26.434Z",
  "lastName": "Yeh",
  "location": {
    "address1": "",
    "address2": "",
    "address3": "",
    "address4": "",
    "city": "",
    "countryCode": "",
    "stateCode": "",
    "zipCode": ""
  },
  "middleName": "",
  "mobilePhone": "",
  "phone": "",
  "postalAddress": {
    "addrDefined": false,
    "addrErrorCount": 0,
    "addrLastCheck": "",
    "addrQuality": "0",
    "line1": "Baiz Yeh",
    "line2": "",
    "line3": "",
    "line4": "",
    "line5": "",
    "line6": "",
    "serialized": "Yeh Baiz\n\n\n\n\n"
  },
  "preferredLanguage": "none",
  "salutation": "",
  "subscriptions": {
    "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/"
  },
  "thumbnail": "/nl/img/thumbnails/defaultProfil.png",
  "timeZone": "none",
  "title": "Yeh Baiz (user@example.com)"
}

プロフィールにサービスの購読を行うには、次の API を使用します。

サンプルコード:プロフィールの購読

$url = "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/";
$subscriptionPKey = "@xxxxxx";
$response = \Httpful\Request::post($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->body('{"service":{"PKey":"' .$subscriptionPKey. '"}}')
  ->send();

$updatedProfile = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。

サンプルコード:プロフィールにサービスの購読を行う

HTTP/1.1 201 Created
{
  "PKey": "@xxxxxx",
  "created": "2018-12-13 08:26:52.291Z",
  "email": "",
  "expirationDate": "",
  "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/@xxxxxx",
  "mobilePhone": "",
  "origin": "",
  "service": {
      "PKey": "@xxxxxx",
      "title": "メルマガ (mail-mag)"
  },
  "serviceName": "",
  "subscriber": {
      "PKey": "@xxxxxx",
      "title": "Yeh Baiz (user@example.com)"
  },
  "subscriptionDate": "",
  "title": ""
}

メルマガ登録状況の確認

一つのプロフィールに複数のサービスが紐付いてる可能性があるので、プロフィールの購読状況の配列に対しループでメルマガに登録されているかを確認します。

プロフィールに紐ついた購読の一覧を取得するには、以下の API を使用します。

サンプルコード:プロフィールに紐ついた購読の一覧を取得

$url = "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/";

$response = \Httpful\Request::get($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$profileSubscription = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。「content」にある各配列はそのプロフィールが購読しているサービスを表していて、「content[n].href」はそのサービスから購読を解除するための API エンドポイントになります。

Response body

HTTP/1.1 200 OK
{
  "content": [
    {
      "PKey": "@xxxxxx",
      "created": "2018-12-13 08:26:52.291Z",
      "email": "",
      "expirationDate": "",
      "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/@xxxxxx",
      "metadata": "subscription",
      "mobilePhone": "",
      "origin": "",
      "service": {
        "PKey": "@xxxxxx",
        "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/service/@xxxxxx",
        "label": "メルマガ",
        "name": "mail-mag",
        "title": "メルマガ (mail-mag)"
      },
      "serviceName": "mail-mag",
      "subscriber": {
        "PKey": "@xxxxxx",
        "email": "user@example.com",
        "firstName": "Yeh",
        "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx",
        "lastName": "Baiz",
        "title": "Yeh Baiz (user@example.com)"
      },
      "subscriptionDate": "",
      "title": "メルマガ (mail-mag) / Yeh Baiz (user@example.com)"
    }
  ],
  "count": {
    "href": "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions//_count?_lineStart=@xxxxxx",
    "value": 1
  },
  "serverSidePagination": true
}

メルマガの解除

プロフィールに紐ついた購読を削除するには、次の API を使用します。

サンプルコード:サービス購読の解除

$url = "https://mc.adobe.io/TENANT-ID/campaign/profileAndServices/profile/@xxxxxx/subscriptions/@xxxxxx";

$response = \Httpful\Request::delete($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$profileSubscription = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。

Response body

HTTP/1.1 204 No Content

ワークフローの起動と自動化

API により外部のトリガからワークフローを実行するための設定方法(Campaign 側設定)

  • 起動したいワークフローを開き、上にある internal name をメモします
  • 左側でアクティビティの一覧から、Execution ➔ External signal を選択し、ワークフローに配置します
  • 配置した External signal を開き、internal name をメモします

サンプルコード

先ほどメモした、ワークフローと External signal の internal name を設定してから実行してください。

ワークフローの Signal アクティビティを調べるには、次の API を使用します。

サンプルコード:ワークフローの Signal アクティビティを調べる

$url = "https://mc.adobe.io/" . $tenantId . "/campaign/workflow/execution/my-workflow";

$response = \Httpful\Request::get($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$signalActivity = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。「activities.activity.signaln.trigger.href」の URL はその Signal を起動するための API エンドポイントになります。

Response body

HTTP/1.1 204 OK
{
  "PKey": "@xxxxxx",
  "activities": {
    "activity": {
      "signal2": {
        "PKey": "@xxxxxx",
        "affinity": "",
        "collision": "multiple",
        "comment": "",
        "errorLimit": 0,
        "errors": "",
        "form": "signal",
        "img": "xtk:activities/signal.svg",
        "label": "Signal from CRM systems",
        "mainSet": "",
        "mask": "none",
        "maxExecutionTime": "PT0S",
        "name": "signal2",
        "onError": "suspend",
        "runOnSimulation": true,
        "src": "",
        "srcTargetResource": "",
        "tempMetadata": {
          "href": "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/activities/activity/@xxxxxx/tempMetadata/"
        },
        "timezone": "_inherit_",
        "title": "Signal from CRM systems (signal2)",
        "trigger": {
          "href": "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/activities/activity/@xxxxxx/trigger/"
        },
        "x": 140,
        "xpathEditTarget": "",
        "y": 220
      },
      "signal3": {
        "PKey": "@xxxxxx",
        "affinity": "",
        "collision": "multiple",
        "comment": "",
        "errorLimit": 0,
        "errors": "",
        "form": "signal",
        "img": "xtk:activities/signal.svg",
        "label": "Signal from reward systems",
        "mainSet": "",
        "mask": "none",
        "maxExecutionTime": "PT0S",
        "name": "signal3",
        "onError": "suspend",
        "runOnSimulation": true,
        "src": "",
        "srcTargetResource": "",
        "tempMetadata": {
          "href": "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/activities/activity/@xxxxxx/tempMetadata/"
        },
        "timezone": "_inherit_",
        "title": "Signal from reward systems (signal3)",
        "trigger": {
          "href": "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/activities/activity/@xxxxxx/trigger/"
        },
        "x": 220,
        "xpathEditTarget": "",
        "y": 330
      }
    }
  },
  "advanced": false,
  "builtIn": false,
  "commands": {
    "href": "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/commands/"
  },
  "created": "2018-04-17 06:51:56.747Z",
  "desc": "",
  "icon": "workflow",
  "isExternal": false,
  "isMaster": false,
  "isTemplate": false,
  "label": "プロフィールをインポート",
  "lastModified": "2018-12-14 01:45:25.150Z",
  "logicalStatus": "started",
  "mainDate": "2018-11-06 07:55:45.953Z",
  "name": "profile-import",
  "nextProcessingDate": "2019-11-06 07:55:45.955Z",
  "state": "started",
  "template": {
    "PKey": "@xxxxxx",
    "title": "New workflow (newWorkflow)"
  },
  "thumbnail": "/nl/img/thumbnails/defaultWorkflow.png",
  "title": "プロフィールをインポート (profile-import)",
  "type": "standard"
}

Signal アクティビティを起動するには、次の API を使用します。

サンプルコード:Signal アクティビティの起動

$url = "https://mc.adobe.io/TENANT-ID/campaign/workflow/execution/@xxxxxx/activities/activity/@xxxxxx/trigger/";

$response = \Httpful\Request::post($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json',
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->body('{"source":"会員データ書き出し処理完了"}')
  ->send();

$triggerResult = json_decode($response->raw_body, true);

トランザクションメールの送信

このユースケースを実現するには、Campaign 側で2つの設定が必要です。

  1. 買い物かごを放棄すると Campaign に送信されるリアルタイムイベントの定義と設定
  2. 上記イベントを受信すると送信するトランザクションメールのテンプレートの編集

トランザクションメールの設定(Campaign 側設定)

リアルタイムイベントの定義と設定

  1. インスタンスの左上隅にあるバナーをクリックしナビゲーションを開きます
  2. Marketing plans ➔ Transactional messages ➔ Event configuration の順でクリックします
  3. Create をクリックし新しいイベントを作成します
  4. リアルタイムイベントの名前と ID を定義します。ID は「EVT」で始める必要があります。「EVT」が無ければ自動的に追加されます
  5. Fields のところで、Add field をクリックしてイベントのプロパティになる属性を設定します
  6. 次の画面で属性と値の型を定義し、Add をクリックします

    • この作業を繰り返し、以下4つの属性を作成します
      • Label ID Type
        FirstName Text
        LastName Text
        最終閲覧商品 LastViewedProduct Text
        カート内商品数 ProductsInCart Text
  7. 次は下にスクロールし、Enrichment のところで Add element をクリックします
  8. New enrichment の画面で、メールの中で参照したいリソースを選択し、ID を定義します。このユースケースではメールで受信者のプロフィールを参照したいので、リソースは Profile を選択します
  9. 次はイベントのデータからプロフィールと紐付けるために、イベントの属性とプロフィールの属性を関連付けます。

    • この作業を繰り返し、以下2つの関連付けを追加します
      • Source path Destination path
        FirstName firstName
        LastName LastName
    • ここでは、概念実証のために姓と名だけで紐付きますが、実際にはメンバーのユニーク ID など重複しない ID を設定する事をおすすめします
  10. 問題なく設定しましたら、上にスクロールし、Publish をクリックしてこのイベントを発行(有効化)します。進捗が 100% になると発行完了です。
  11. API preview をクリックして、後ほどの API コールで必要な固有パラメータ transactionalAPI をメモします。これはトランザクションメールの API エンドポイントになります。

トランザクションメールのテンプレートの編集

  1. インスタンスの左上隅にあるバナーをクリックしナビゲーションを開きます
  2. Marketing plans ➔ Transactional messages ➔ Transactional messages の順でクリックします
  3. リアルタイムイベントを作成するとそれに対応するトランザクションメールのテンプレートも作成されますので、カート放棄のテンプレートを開きます
  4. Content のところから、メールエディターをクリックしてテンプレートを編集します
  5. 編集の要領は通常メールを編集するときと同じですが、リアルタイムイベントの値を使ってパーソナライズする場合は、Event context (ctx) のノードから選択します
  6. 問題なく設定しましたら、上にスクロールし、Publish をクリックしてこのイベントを発行(有効化)します。進捗が 100% になると発行完了です

サンプルコード

$realTimeEventID と $transactionalAPI に先ほど設定した値を指定します。一連の設定作業が正しく完了されれば、トランザクションイベントに指定したメールアドレスにパーソナライズしたメールが送信され、トランザクションイベントの送信結果にはリアルタイムイベントに紐づけたプロフィールの詳細が含まれます。

トランザクションイベントを送信するには、次の API を使用します。

サンプルコード:トランザクションイベントを送信する

$url = 'https://mc.adobe.io/' . $tenantId . '/campaign/' . $transactionalAPI . '/' . $realTimeEventID;
$realTimeEvent = '{
  "email": "user@example.com",
  "ctx": {
    "ProductsInCart": "2",
    "LastViewedProduct": "タイタニック",
    "FirstName": "Baiz",
    "LastName": "Yeh"
  }
}';

$response = \Httpful\Request::post($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json;charset=utf-8', // ;charset=utf-8 を追記を忘れずに
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->body($realTimeEvent)
  ->send();

$rtResponse = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。「href」は、トランザクションイベントの送信結果を確認する際に使用する API エンドポイントになります。また、トランザクションメールが正しく送信された事も確認してください。

Response body

HTTP/1.1 201 Created
{
  "PKey": "@xxxxxx",
  "application": "",
  "countryCode": "",
  "created": "",
  "ctx": {
    "FirstName": "",
    "LastName": "",
    "LastViewedProduct": "",
    "ProductsInCart": "",
    "profile_enrichment": {
      "PKey": "",
      "age": 0,
      "birthDate": "",
      "blackList": false,
      "blackListEmail": false,
      "blackListFax": false,
      "blackListMobile": false,
      "blackListPhone": false,
      "blackListPostalMail": false,
      "blackListPushnotification": false,
      "created": "",
      "cryptedId": "",
      "domain": "",
      "email": "",
      "emailFormat": "unknown",
      "fax": "",
      "firstName": "",
      "gender": "unknown",
      "isExternal": false,
      "lastModified": "",
      "lastName": "",
      "location": {
        "address1": "",
        "address2": "",
        "address3": "",
        "address4": "",
        "city": "",
        "countryCode": "",
        "stateCode": "",
        "zipCode": ""
      },
      "middleName": "",
      "mobilePhone": "",
      "phone": "",
      "postalAddress": {
        "addrDefined": false,
        "addrErrorCount": 0,
        "addrLastCheck": "",
        "addrQuality": "0",
        "line1": "",
        "line2": "",
        "line3": "",
        "line4": "",
        "line5": "",
        "line6": "",
        "serialized": ""
      },
      "preferredLanguage": "none",
      "salutation": "",
      "thumbnail": "",
      "timeZone": "none",
      "title": ""
    }
  },
  "email": "",
  "expiration": "",
  "externalId": "",
  "externalId2": "",
  "externalId3": "",
  "externalId4": "",
  "externalId5": "",
  "href": "https://mc.adobe.io/TENANT-ID/campaign/transactional-api-xxx/realtime-event-id/@xxxxxx",
  "marketingCloudId": "",
  "mobilePhone": "",
  "postalAddress": {
    "addrDefined": false,
    "line1": "",
    "line2": "",
    "line3": "",
    "line4": "",
    "line5": "",
    "line6": "",
    "line7": "",
    "serialized": ""
  },
  "processed": "",
  "pushPlatform": "apns",
  "registrationToken": "",
  "scheduled": "",
  "serverUrl": "",
  "status": "pending",
  "title": ""
}

トランザクションイベントの送信結果を確認するには、次の API を使用します。トランザクションイベントを送信してから結果が反映されるまで数秒かかりますので、実行の間隔を設けると良いと思います。

サンプルコード:トランザクションイベントの送信結果の確認

$url = "https://mc.adobe.io/TENANT-ID/campaign/transactional-api-xxx/realtime-event-id/@xxxxxx";

$response = \Httpful\Request::get($url)
  ->addHeaders(array(
    'Cache-Control'  => 'no-cache',
    'Content-Type'  => 'application/json;charset=utf-8', // ;charset=utf-8 を追記を忘れずに
    'Authorization'  => 'Bearer ' . $token['access_token'],
    'X-Api-Key'  => $client['id'],
    ))
  ->send();

$rtResponse = json_decode($response->raw_body, true);

このリクエストの期待されるレスポンスは下記の通りです。リアルタイムイベントに紐づけたプロフィールの詳細が含まれるはずです。この出力では、プロフィールに携帯番号が含まれるのが分かります。

Response body

HTTP/1.1 200 OK
{
  "PKey": "@xxxxxx",
  "application": "",
  "countryCode": "",
  "created": "2018-12-14 02:56:11.848Z",
  "ctx": {
    "FirstName": "Baiz",
    "LastName": "Yeh",
    "LastViewedProduct": "タイタニック",
    "ProductsInCart": "2",
    "profile_enrichment": {
      "PKey": "",
      "age": 0,
      "birthDate": "",
      "blackList": false,
      "blackListEmail": false,
      "blackListFax": false,
      "blackListMobile": false,
      "blackListPhone": false,
      "blackListPostalMail": false,
      "blackListPushnotification": false,
      "created": "2018-11-05 07:52:08.176Z",
      "cryptedId": "",
      "domain": "example.com",
      "email": "user@example.com",
      "emailFormat": "unknown",
      "fax": "",
      "firstName": "Baiz",
      "gender": "unknown",
      "isExternal": false,
      "lastModified": "2018-11-05 07:52:24.250Z",
      "lastName": "Yeh",
      "location": {
        "address1": "",
        "address2": "",
        "address3": "",
        "address4": "",
        "city": "",
        "countryCode": "",
        "stateCode": "",
        "zipCode": ""
      },
      "middleName": "",
      "mobilePhone": "123456789",
      "phone": "",
      "postalAddress": {
        "addrDefined": false,
        "addrErrorCount": 0,
        "addrLastCheck": "",
        "addrQuality": "0",
        "line1": "Baiz YEH",
        "line2": "",
        "line3": "",
        "line4": "",
        "line5": "",
        "line6": "",
        "serialized": "Baiz YEH\n\n\n\n\n"
      },
      "preferredLanguage": "none",
      "salutation": "",
      "thumbnail": "http://TENANT-ID-res.adobe-campaign.com/res/TENANT-ID/xxxxxx.jpg",
      "timeZone": "none",
      "title": "Baiz Yeh (user@example.com)"
    }
  },
  "email": "user@example.com",
  "expiration": "",
  "externalId": "",
  "externalId2": "",
  "externalId3": "",
  "externalId4": "",
  "externalId5": "",
  "marketingCloudId": "",
  "mobilePhone": "",
  "postalAddress": {
    "addrDefined": false,
    "line1": "",
    "line2": "",
    "line3": "",
    "line4": "",
    "line5": "",
    "line6": "",
    "line7": "",
    "serialized": "\n\n\n\n\n\n"
  },
  "processed": "2018-12-14 02:56:26.760Z",
  "pushPlatform": "apns",
  "registrationToken": "",
  "scheduled": "2018-12-14 02:56:11.848Z",
  "serverUrl": "",
  "status": "processed",
  "title": "realtime-event-id (user@example.com)"
}

最後に

以上、3つの課題に絞って Adobe Campaign Standard の API の実装方法を紹介しましたが、いかがでしたでしょうか。公式ドキュメントでは、この記事でカバーしていない GDPR の処理やデリバリー関連のデータ取得など、強力な API を沢山ご用意しておりますので、実装のお役に立てればと思います。少し長い記事となりましたが、最後までお読み頂きありがとうございました。

その他のトピック

関連リンク

本記事の内容は2018年12月現在の情報のものであり、今後変更される可能性があります。あらかじめご了承ください。

POSTED ON 2019.01.23