概要
AWS Marketplaceのインテグレーション実装では、購入者がサブスクライブした際の顧客登録と、エンタイトルメント(契約内容)の確認を実装する必要があります。 Deshimaを使う場合、この実装は不要です。 AWSセラーアカウントを連携するだけで、必要なAWSリソースが自動設定されます。セキュリティ要件について: AWS Marketplaceでは2025年4月30日よりSaaS製品に対する必須セキュリティポリシー(10項目)が施行されています。インテグレーション実装と並行して、製品自体のセキュリティ対策も確認してください。詳細は SaaS セキュリティ要件 を参照してください。
- Deshimaを使う場合
- 手動で実装する場合
Deshima AWS連携の手順
1
ホーム画面の「AWS連携状態」エリアから連携画面に進む
Deshimaホームに「AWS連携状態」エリアがあります。「AWS連携設定へ」ボタンを押下してAWS連携設定画面に進みます。

2
CloudFormationスタックを作成してRoleArnを取得する
AWS連携設定画面の手順に従って操作します。
- 出品先のAWS Seller Accountにログインした状態で「CloudFormationを開く」ボタンを押下します(us-east-1リージョンにスタックが作成されます)
- AWSコンソールのCloudFormation画面でスタックを作成します
- スタック作成完了後、「出力」タブを開きます
-
RoleArnの値をコピーします

3
RoleArnとメール通知設定を入力して接続を確認する
Deshimaの連携設定画面に戻り、以下を入力して「接続を確認する」ボタンを押下します。

| 入力項目 | 内容 |
|---|---|
| RoleArn | CloudFormationの出力タブからコピーした値(arn:aws:iam::XXXXXXXXXXXX:role/DeshimaIntegrationRole) |
| 送信元メールアドレス | 購入者・オペレーターへの通知メールの送信元アドレス |
| オペレーター通知先メールアドレス | 新規購入・契約変更・解約などのイベント通知を受け取るメールアドレス |
送信元メールアドレスにはAmazon SESの本番環境への移行(サンドボックス解除)が必要です。 セラーアカウント上のSESを使用するため、事前にサンドボックスモードを解除してください。未解除の場合、購入者への通知メールが送信されません。
Amazon SES 本番環境への移行 →

4
連携完了とAWSリソース自動設定を確認する
接続確認が成功すると、連携ステータスが「連携済み」に変わります。以下のAWSリソースが自動設定されており、手動でのコード実装は不要です。

| AWSリソース | 役割 |
|---|---|
| IAMロール(DeshimaIntegrationRole) | DeshimaがResolveCustomer・GetEntitlementsを呼び出すための権限 |
| IAMロール(DeshimaEventBridgeForwardRole) | EventBridgeがDeshimaのEventBusにイベントを転送するための権限 |
| EventBridgeルール(契約更新) | License Updated - Manufacturer イベントをDeshimaに転送 |
| EventBridgeルール(契約解除) | License Deprovisioned - Manufacturer イベントをDeshimaに転送 |
Concurrent Agreements対応済み: DeshimaはLicenseArnベースのエンタイトルメント管理に対応しています。2026年6月1日以降の新規製品要件を満たしています。

5
Fulfillment URLを確認してAWS Marketplaceに登録する
連携完了後の画面に、購入者がサブスクライブした際にリダイレクトされる Fulfillment URL が表示されます。このURLをAWS Marketplace Management Portalの製品設定画面に登録します。

AWS Marketplace Management Portalでの製品情報登録についてはPhase 4: 公開フェーズで解説します。
手動実装は難易度が高く(⭐⭐⭐⭐⭐)、相当な実装工数が必要です。Deshimaを使う場合、このタブの実装は不要です。 より高度なカスタマイズが必要な場合はプロフェッショナルサービスもご利用いただけます。
概要
AWS Marketplaceは、製品とAWS Marketplaceを接続するための3つの主要APIを提供します。Deshimaが取り扱うSaaS Contractでは、ResolveCustomer(顧客登録)と GetEntitlements(エンタイトルメント確認)を使用します。BatchMeterUsage(従量課金メータリング)はContractでは使用しません。3つの主要API
ResolveCustomer
顧客登録登録トークンを検証し、顧客情報を取得
BatchMeterUsage
従量課金(Contract対象外)Subscription/Consumption向け。Contractでは未使用
GetEntitlements
エンタイトルメント契約内容を確認
Concurrent Agreements対応(2026年6月1日〜新製品必須)
重要: 2026年6月1日以降、すべての新規SaaS製品はConcurrent Agreementsへの対応が必須となっています。この要件は現在有効であり、新規製品を作成する場合は必ず対応が必要です。これにより、同一AWSアカウントで同一製品の複数購入が可能になります。
CustomerIdentifier 非推奨: 2026年6月1日以降に作成された新規製品では、CustomerIdentifier は非推奨です。顧客の識別には CustomerAWSAccountId を使用してください。既存製品では引き続き CustomerIdentifier を使用できますが、新規製品では CustomerAWSAccountId と LicenseArn の組み合わせで契約を識別します。変更点
Concurrent Agreements対応では、契約(エンタイトルメント)の識別がProductCodeベースからLicenseArnベースに変わります。同一AWSアカウントで同一製品の複数契約が可能になります。| 項目 | 従来 | Concurrent Agreements対応 |
|---|---|---|
| ResolveCustomerレスポンス | CustomerIdentifier, ProductCode, CustomerAWSAccountId | + LicenseArn |
| 契約・利用の識別子 | ProductCode + CustomerIdentifier | LicenseArn |
| 同一アカウントの複数購入 | 不可 | 可能 |
実装上の注意
ResolveCustomerAPIのレスポンスに含まれるLicenseArnを必ず保存してください- Contractでは
LicenseArn単位でエンタイトルメント(GetEntitlements)を確認します(従量課金のメータリングは行いません) - 同一時間帯にProductCodeベースとLicenseArnベースのメータリングを混在させると二重課金が発生します
ResolveCustomerレスポンス(Concurrent Agreements対応)
{
"CustomerIdentifier": "cust_abc123",
"ProductCode": "your-product-code",
"CustomerAWSAccountId": "123456789012",
"LicenseArn": "arn:aws:license-manager::123456789012:license/lic-abc123"
}
既存製品のConcurrent Agreements有効化
2026年6月1日より前に作成された既存製品でConcurrent Agreementsを有効化するには、以下の2つの方法があります。方法1: AWSサポートへ問い合わせる
方法1: AWSサポートへ問い合わせる
既存製品でアクティブなサブスクライバーがいる場合、AWS Marketplace「Contact Us」フォームから有効化を依頼できます。フォームの選択項目:
- カテゴリ: Commercial Marketplace
- サブカテゴリ: Product Configuration / Integration
- 件名: Concurrent Agreements Support
Hello,
I have an existing AWS Marketplace SaaS product with active subscribers and would like to discuss enabling Concurrent Agreements.
Seller Account ID: [Your seller account ID]
Product ID: [Your product ID]
Number of active agreements: [Number]
I am unable to create a new listing because:
[Explain the specific circumstances that prevent you from creating a new listing and migrating customers]
Thank you.
AWSチームがアカウントの状況を確認し、有効化の手順を案内します。承認後は、Amazon SNSからAmazon EventBridgeへの移行、
LicenseArnをサポートするAPI統合の更新、データベーススキーマの更新(LicenseArnを主キーに)などを実施します。方法2: 新規リスティングを作成して移行する
方法2: 新規リスティングを作成して移行する
Concurrent Agreementsが有効な新規リスティングを作成し、既存の顧客を段階的に移行する方法です。
- 新規リスティングではConcurrent Agreementsがデフォルトで有効になっています
- 既存顧客には契約更新のタイミングで新規リスティングへの移行を案内します
- 旧リスティングは全顧客の移行完了後に非公開にします
新規リスティングへの移行が可能な場合、こちらの方法がよりシンプルで推奨されます。
テナントプロビジョニング戦略
Concurrent Agreementsでは、同一顧客が同一製品で複数のライセンスを持つことができます。テナント構成には3つの戦略があります。| 戦略 | 説明 | 適用シーン |
|---|---|---|
| 個別テナント | ライセンスごとに独立したテナントを作成 | 部門ごとの分離が必要な組織 |
| 統合テナント | 複数ライセンスを単一テナントにマッピング | 請求を一元管理したい組織 |
| 購入者選択 | 購入者がテナント構成を選択可能 | 最大限の柔軟性を提供したい場合 |
既存顧客のLicenseArnバックフィル
Concurrent Agreementsへの移行時、既存顧客のLicenseArn マッピングが必要です。- 契約ベース(Contract / Contract with Consumption):
GetEntitlementsAPIのCUSTOMER_AWS_ACCOUNT_IDフィルターを使用してバックフィル - 従量課金のみ: AWSが提供するS3バケットのCSVファイル(CustomerIdentifier, CustomerAWSAccountId, AgreementId, LicenseArn)を使用
GetEntitlements - LICENSE_ARNフィルター
Concurrent Agreementsが有効な製品では、LICENSE_ARN フィルターを使用して特定の契約のエンタイトルメントを取得できます。{
"ProductCode": "your-product-code",
"Filter": {
"LICENSE_ARN": ["arn:aws:license-manager::123456789012:license/lic-abc123"]
}
}
ResolveCustomer API
顧客がAWS Marketplaceから製品を購入した際に発行される登録トークンを検証し、顧客情報を取得します。オンボーディングフロー全体
購入から顧客アカウント有効化までの流れは次の通りです。- 顧客がAWS Marketplaceで製品をサブスクライブする
- 顧客のブラウザが、登録ランディングページURL(Fulfillment URL)へ
POSTリクエストされる。このリクエストにはx-amzn-marketplace-token(登録トークン)が含まれる- 無料トライアルからの購入の場合、追加で
x-amzn-marketplace-offer-type=free-trialパラメータが付与される
- 無料トライアルからの購入の場合、追加で
- ランディングページは受け取ったトークンを ResolveCustomer に渡し、顧客情報を解決する
- 顧客に対し、製品内でのアカウント作成または既存アカウントへのサインインを促す
subscribe-success を受信するまでサブスクリプションを有効化しないでください。 この通知を受け取る前に送信したメータリングレコードは計上されません。サブスクリプション通知(EventBridge / 旧SNS)で subscribe-success を受信してから顧客アカウントを有効化します。顧客識別子はAWS account IDを推奨: 新規実装または統合の更新では、
CustomerIdentifier ではなく customer AWS account ID を顧客の主キーとして使用することがAWSから推奨されています。アカウントDBにはAWS account ID列を設け、同一AWS account IDが重複しないことを検証してください。登録トークンは約1時間で失効しうるため、受信後ただちにResolveCustomerを呼び出してください。API仕様
エンドポイント:POST https://metering.marketplace.us-east-1.amazonaws.com/
X-Amz-Target: AWSMPMeteringService.ResolveCustomer
{
"RegistrationToken": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}
{
"CustomerIdentifier": "cust_abc123",
"ProductCode": "your-product-code",
"CustomerAWSAccountId": "123456789012"
}
実装例
- Node.js
- TypeScript
- Python
const AWS = require('aws-sdk');
const marketplace = new AWS.MarketplaceMetering({
region: 'us-east-1'
});
async function resolveCustomer(registrationToken) {
const params = {
RegistrationToken: registrationToken
};
try {
const result = await marketplace.resolveCustomer(params).promise();
console.log('Customer resolved successfully');
console.log('Customer ID:', result.CustomerIdentifier);
console.log('Product Code:', result.ProductCode);
console.log('AWS Account ID:', result.CustomerAWSAccountId);
return {
customerId: result.CustomerIdentifier,
productCode: result.ProductCode,
awsAccountId: result.CustomerAWSAccountId
};
} catch (error) {
console.error('ResolveCustomer failed:', error);
if (error.code === 'InvalidTokenException') {
throw new Error('Invalid registration token');
} else if (error.code === 'ExpiredTokenException') {
throw new Error('Registration token has expired');
} else if (error.code === 'ThrottlingException') {
await new Promise(resolve => setTimeout(resolve, 1000));
return resolveCustomer(registrationToken);
}
throw error;
}
}
// 使用例
const customer = await resolveCustomer('eyJ0eXAiOiJKV1QiLCJhbGc...');
console.log('Customer:', customer);
import { MarketplaceMetering } from 'aws-sdk';
interface CustomerInfo {
customerId: string;
productCode: string;
awsAccountId: string;
}
const marketplace = new MarketplaceMetering({
region: 'us-east-1'
});
async function resolveCustomer(
registrationToken: string
): Promise<CustomerInfo> {
const params: MarketplaceMetering.ResolveCustomerRequest = {
RegistrationToken: registrationToken
};
try {
const result = await marketplace.resolveCustomer(params).promise();
if (!result.CustomerIdentifier || !result.ProductCode) {
throw new Error('Invalid response from ResolveCustomer API');
}
return {
customerId: result.CustomerIdentifier,
productCode: result.ProductCode,
awsAccountId: result.CustomerAWSAccountId || ''
};
} catch (error) {
console.error('ResolveCustomer failed:', error);
throw error;
}
}
import boto3
from typing import Dict
marketplace = boto3.client('meteringmarketplace', region_name='us-east-1')
def resolve_customer(registration_token: str) -> Dict[str, str]:
"""
登録トークンを検証し、顧客情報を取得
Args:
registration_token: AWS Marketplaceの登録トークン
Returns:
顧客情報の辞書
Raises:
Exception: API呼び出しが失敗した場合
"""
try:
response = marketplace.resolve_customer(
RegistrationToken=registration_token
)
print('Customer resolved successfully')
print(f"Customer ID: {response['CustomerIdentifier']}")
print(f"Product Code: {response['ProductCode']}")
print(f"AWS Account ID: {response.get('CustomerAWSAccountId', 'N/A')}")
return {
'customer_id': response['CustomerIdentifier'],
'product_code': response['ProductCode'],
'aws_account_id': response.get('CustomerAWSAccountId', '')
}
except Exception as e:
print(f'ResolveCustomer failed: {str(e)}')
error_code = e.response.get('Error', {}).get('Code', '')
if error_code == 'InvalidTokenException':
raise Exception('Invalid registration token')
elif error_code == 'ExpiredTokenException':
raise Exception('Registration token has expired')
raise
# 使用例
customer = resolve_customer('eyJ0eXAiOiJKV1QiLCJhbGc...')
print('Customer:', customer)
エラーハンドリング
InvalidTokenException
InvalidTokenException
原因: 登録トークンが無効または不正な形式対処方法:
- トークンの形式を確認
- トークンが改変されていないか確認
- 顧客に再度購入プロセスを実行してもらう
ExpiredTokenException
ExpiredTokenException
原因: 登録トークンの有効期限が切れている対処方法:
- トークンの有効期限は4時間
- 顧客に新しいトークンを取得してもらう
- 購入プロセスを再度実行
ThrottlingException
ThrottlingException
原因: API呼び出しレートが制限を超えている対処方法:
async function resolveCustomerWithRetry(token, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await resolveCustomer(token);
} catch (error) {
if (error.code === 'ThrottlingException' && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
BatchMeterUsage API(Contract対象外)
BatchMeterUsage は従量課金(Subscription / Contract with Consumption)向けのメータリングAPIです。Deshimaが取り扱うSaaS Contractでは使用しません。本ガイドでは実装手順を扱いません。
メータリング統合向けAIエージェントスキルが一般提供(GA)になりました(2026年9月30日)。 AWS MCP Serverを通じて提供されるAIガイド型の体験で、Amazon Q Developer や Kiro などMCP対応のAIコーディングアシスタントからそのまま利用できます(プラグインのインストールは不要)。このスキルは従量課金(Subscription / Contract with Consumption)のメータリング統合を対象とし、製品タイプ・価格モデル・使用量ディメンションのヒアリング、メータリングAPIの推奨、統合コードとCloudFormationスタックの生成、AWS Marketplaceに対するエンドツーエンドテスト、ResolveCustomer / BatchMeterUsage / EventBridge を用いたサーバーレス・メータリングパイプラインのデプロイ(Concurrent Agreements対応)を支援します。なお、Deshimaのマネージド連携は現時点でメータリングを伴わないSaaS Contractを対象としています。 そのため、このスキルは従量課金のメータリング統合を手動やプロフェッショナルサービスで実装するセラー向けの参考情報です。詳細: SaaSサブスクリプションの従量課金メータリング設定(AWS公式) / AWSの発表
GetEntitlements API
契約ベース製品のエンタイトルメント(契約内容)を確認します。API仕様
エンドポイント:POST https://entitlement.marketplace.us-east-1.amazonaws.com/
X-Amz-Target: AWSMPEntitlementService.GetEntitlements
{
"ProductCode": "your-product-code",
"Filter": {
"CUSTOMER_IDENTIFIER": ["cust_abc123"]
}
}
{
"Entitlements": [
{
"ProductCode": "your-product-code",
"Dimension": "Users",
"CustomerIdentifier": "cust_abc123",
"Value": {
"IntegerValue": 100
},
"ExpirationDate": "2025-01-17T10:00:00Z"
}
]
}
実装例
- Node.js
- キャッシング
- Express Middleware
const AWS = require('aws-sdk');
const entitlement = new AWS.MarketplaceEntitlementService({
region: 'us-east-1'
});
async function getEntitlements(customerId) {
const params = {
ProductCode: process.env.AWS_MARKETPLACE_PRODUCT_CODE,
Filter: {
CUSTOMER_IDENTIFIER: [customerId]
}
};
try {
const result = await entitlement.getEntitlements(params).promise();
console.log(`Found ${result.Entitlements.length} entitlements`);
const entitlements = {};
for (const ent of result.Entitlements) {
entitlements[ent.Dimension] = {
value: ent.Value.IntegerValue || ent.Value.StringValue || ent.Value.BooleanValue,
expirationDate: ent.ExpirationDate
};
}
return entitlements;
} catch (error) {
console.error('GetEntitlements failed:', error);
throw error;
}
}
async function validateAccess(customerId, feature) {
const entitlements = await getEntitlements(customerId);
if (!entitlements[feature]) {
return false;
}
if (entitlements[feature].expirationDate) {
const expiration = new Date(entitlements[feature].expirationDate);
if (expiration < new Date()) {
return false;
}
}
return entitlements[feature].value > 0;
}
// 使用例
const hasProFeatures = await validateAccess('cust_abc123', 'ProFeatures');
if (hasProFeatures) {
console.log('Access granted to Pro features');
} else {
console.log('Access denied');
}
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 3600 }); // 1時間キャッシュ
async function getCachedEntitlements(customerId) {
const cacheKey = `entitlements:${customerId}`;
let entitlements = cache.get(cacheKey);
if (!entitlements) {
entitlements = await getEntitlements(customerId);
cache.set(cacheKey, entitlements);
console.log('Entitlements cached for:', customerId);
} else {
console.log('Entitlements retrieved from cache');
}
return entitlements;
}
function invalidateEntitlementsCache(customerId) {
const cacheKey = `entitlements:${customerId}`;
cache.del(cacheKey);
console.log('Cache invalidated for:', customerId);
}
function requireEntitlement(dimension) {
return async (req, res, next) => {
const customerId = req.headers['x-customer-id'];
if (!customerId) {
return res.status(401).json({ error: 'Customer ID required' });
}
try {
const hasAccess = await validateAccess(customerId, dimension);
if (!hasAccess) {
return res.status(403).json({
error: 'Access denied',
message: `This feature requires ${dimension} entitlement`
});
}
req.entitlements = await getCachedEntitlements(customerId);
next();
} catch (error) {
console.error('Entitlement check failed:', error);
return res.status(500).json({ error: 'Internal server error' });
}
};
}
// 使用例
app.get('/api/pro-feature',
requireEntitlement('ProFeatures'),
(req, res) => {
res.json({ message: 'Pro feature accessed' });
}
);
EventBridgeによるイベント通知
AWS Marketplaceは顧客のサブスクリプションイベント(契約更新・解除)をEventBridge経由で配信します。以下の設定が必要です。受信するイベント
| イベント | タイミング |
|---|---|
| License Updated - Manufacturer | 契約更新・変更時 |
| License Deprovisioned - Manufacturer | 契約解除時 |
EventBridgeルールの設定
{
"source": ["aws.agreement-marketplace"],
"detail-type": [
"License Updated - Manufacturer",
"License Deprovisioned - Manufacturer"
]
}
AWS Marketplace Agreements and Licenses のイベントパターンには
source: ["aws.agreement-marketplace"] を必ず含めます。detail-type のみのパターンは他ソースの同名イベントにも一致しうるため、source を指定してください。SNS通知は非推奨です(2027年までにEventBridgeへ完全移行予定)。 新規製品ではEventBridgeで実装してください。SNS通知は
LicenseArn をサポートしていないため、Concurrent Agreementsが有効な製品ではSNSを使用できません。既存製品もEventBridgeへの移行を計画してください。AWS公式ドキュメント →AWS European Sovereign Cloud(ESC)での注意点: ESC(
eusc-de-east-1 パーティション)ではAmazon SNSトピックがサポートされていないため、サブスクリプションイベントの受信にはEventBridgeのみ使用できます。ESCでSaaS製品を掲載する場合、アプリケーションインフラストラクチャおよび全てのAPI連携(ResolveCustomer、GetEntitlements、BatchMeterUsage)を eusc-de-east-1 パーティション内から実行する必要があります。詳細はAWS公式ブログを参照してください。次のステップ
SaaS利用申請フォーム
購入者向けフォームの設定

