Amazon Connectの通話記録をStep Functionsで処理し、Backlogに自動起票させてみた
はじめに
コールセンターシステムの運用において、通話記録の管理とチケット化は業務効率化の重要な要素です。本記事では、Amazon Connectで受けた問い合わせを自動的にBacklogのチケットとして起票する仕組みを、AWS Step Functionsを活用して構築する方法を解説します。
システム構成
全体のアーキテクチャは以下の通りです。

処理の流れは次のようになります。
- Amazon Connectで顧客との通話が行われる
 - エージェントがアフターコールワークを完了すると、問い合わせレコードがAmazon Kinesis Data Streams(以下、KDS)にストリーミングされる
 - EventBridge Pipesがそのデータを検知し、Step Functionsのステートマシンを起動
 - Step Functionsが問い合わせデータを整形し、Backlog APIを呼び出して新規チケットを自動作成
 
この自動化により、Amazon Connectでの通話記録をBacklog上でチケットとして一元管理できるようになります。
作成されるチケットのサンプル
以下は実際に作成されるBacklogチケットの例です。通話の基本情報が整理された形で記録されます。

チケットには通話開始・終了時間、コンタクトID、対応者、発信者電話番号、問い合わせカテゴリなどの情報が含まれ、Amazon Connectのコンタクト詳細ページへのリンクも自動的に設定されます。
Backlog側の事前設定
Backlogにチケットを自動起票するためには、いくつかの事前設定が必要です。ここでは、APIキーの発行と必要なカテゴリー・種別の作成手順を説明します。
Backlog APIキーを発行
まず、BacklogのAPIを利用するためのAPIキーを発行します。
以下の公式ドキュメントに従ってAPIキーを発行してください
発行したAPIキーは後ほどEventBridge Connectionsの設定で使用します。
カテゴリーと種別の作成
種別の作成
通話記録用に「電話」という種別を作成します。

作成後、以下の手順で、プロジェクトIDと「電話」の種別のIDを取得します。
- 対象のプロジェクトページに移動
 - 検索条件で「種別が電話」を設定
 - 表示されたURLから以下の情報をコピー
issueTypeId(例:1075074462)projectId(例:1111111111)- URL例:
https://xxx.backlog.jp/find/xx?allOver=false&issueTypeId=1075074462&limit=20&offset=0&order=false&projectId=1111111111&simpleSearch=true&sort=UPDATED&statusId=4 
- URL例:
 
 

カテゴリーの作成
問い合わせ内容を分類するため、以下の3つのカテゴリーを作成します。
- 商品関連
 - 契約関連
 - その他
 

種別と同様に、各カテゴリーのIDも取得します。
- 検索条件でカテゴリーを選択
 - URLから
componentIdをコピー(これがカテゴリーIDです)- 例:
https://xxx.backlog.jp/find/xx?allOver=false&componentId=222222222222&limit=20&offset=0&order=false&projectId=1111111111&simpleSearch=true&sort=UPDATED&statusId=4 
 - 例:
 
これらのID情報は、後ほどStep Functionsの設定で使用します。
KDS
Amazon Connectから問い合わせレコードをストリーミングするために、KDSを設定します。
KDSは、最小のコストになるようプロビジョンドモードのシャード1で作成します。

Connectのデータストリーミングを有効化します。

これにより、Amazon Connectでの通話終了後、問い合わせレコードが自動的にKDSにストリーミングされるようになります。
Amazon EventBridge Connectionsの設定
Step FunctionsからBacklog APIを安全に呼び出すために、EventBridge Connectionsを使用してAPIキーを管理します。
以下の設定でEventBridge Connectionsを作成します。
- 認証タイプ:APIキー
 - APIキー名と値:使わないので適当な値でよいです
 - 呼び出し Http パラメータ
- パラメータ:クエリ文字列(シークレットのクエリ文字列での可)
 - キー:apiKey
 - 値:作成したBacklog APIキー
 
 

この設定により、Step FunctionsからBacklog APIを呼び出す際に、APIキーを安全に使用できるようになります。
AWS Step Functions の設定
問い合わせレコードを処理してBacklogにチケットを起票するためのステートマシンを作成します。
以下のJSONをStep Functionsのワークフローエディタに貼り付けて、ステートマシンを作成します。各プレースホルダーは自身の環境に合わせて変更してください。

{
  "Comment": "通話記録内容をBacklogチケットに起票する",
  "StartAt": "Base64Decode",
  "States": {
    "Base64Decode": {
      "Type": "Pass",
      "Next": "ChannelTypeCheck",
      "Assign": {
        "decodedData": "{% $base64decode($states.input.data) %}"
      }
    },
    "ChannelTypeCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "InquiryTypeCheck",
          "Condition": "{% $parse($decodedData).Channel = \"VOICE\" %}",
          "Assign": {
            "data": "{% $parse($decodedData) %}"
          }
        }
      ],
      "Default": "NonVoiceChannelEnd"
    },
    "InquiryTypeCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "ExtractContactData",
          "Condition": "{% $data.Attributes.inquiry_type = \"product\" %}",
          "Assign": {
            "categoryId": "<categoryId_product>"
          }
        },
        {
          "Next": "ExtractContactData",
          "Assign": {
            "categoryId": "<categoryId_other>"
          },
          "Condition": "{% $data.Attributes.inquiry_type = \"contract\" %}"
        }
      ],
      "Default": "ExtractContactData",
      "Assign": {
        "categoryId": "<categoryId_contract>"
      }
    },
    "NonVoiceChannelEnd": {
      "Type": "Succeed"
    },
    "ExtractContactData": {
      "Type": "Pass",
      "Assign": {
        "InitialContactId": "{% $data.ContactId %}",
        "InitiationTimestamp": "{% $data.InitiationTimestamp %}",
        "DisconnectTimestamp": "{% $data.DisconnectTimestamp %}",
        "AgentName": "{% $data.Agent.Username %}",
        "CallerPhoneNumber": "{% $data.CustomerEndpoint.Address %}"
      },
      "Next": "FormatContactData"
    },
    "FormatContactData": {
      "Type": "Pass",
      "Assign": {
        "FormattedInitiationTimestamp": "{% $fromMillis($toMillis($InitiationTimestamp), '[Y0001]年[M]月[D]日 [H]時[m01]分[s01]秒', '+0900') %}",
        "FormattedDisconnectTimestamp": "{% $fromMillis($toMillis($DisconnectTimestamp), '[Y0001]年[M]月[D]日 [H]時[m01]分[s01]秒', '+0900') %}",
        "FormattedPhoneNumber": "{% $CallerPhoneNumber = 'anonymous' ? '非通知' : ($substring($CallerPhoneNumber, 0, 3) = '+81' ? '0' & $substring($CallerPhoneNumber, 3) : $CallerPhoneNumber) %}",
        "ContactDetailsURL": "{% 'https://<Connectドメイン>/connect/contact-trace-records/details/' & $InitialContactId & '?tz=Asia/Tokyo' %}"
      },
      "Next": "CreateBacklogTicket"
    },
    "CreateBacklogTicket": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Arguments": {
        "ApiEndpoint": "https://<xxx.backlog.jp>/api/v2/issues",
        "InvocationConfig": {
          "ConnectionArn": "<ConnectionArn>"
        },
        "Headers": {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        "Transform": {
          "RequestBodyEncoding": "URL_ENCODED",
          "RequestEncodingOptions": {
            "ArrayFormat": "INDICES"
          }
        },
        "Method": "POST",
        "RequestBody": {
          "projectId": "<projectId>",
          "summary": "通話記録",
          "description": "{% '|項目|内容|備考|\n|---|---|---|\n|通話開始時間|' & $FormattedInitiationTimestamp & '| |\n|通話終了時間|' & $FormattedDisconnectTimestamp & '| |\n|コンタクトID|' & $InitialContactId & '| [コンタクト詳細URL](' & $ContactDetailsURL & ')|\n|対応者|' & $AgentName & '| |\n|発信者電話番号|' & $FormattedPhoneNumber & '| |' %}",
          "issueTypeId": "<issueTypeId>",
          "categoryId[]": "{% $categoryId %}",
          "priorityId": "3"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "End": true
    }
  },
  "QueryLanguage": "JSONata"
}
FormattedPhoneNumberについては、+81を0に変換しています。非通知の場合はanonymousとなるため、その場合は「非通知」と表示されるよう変換しています。
置き換えが必要なパラメータ
以下のプレースホルダーを実際の値に置き換えてください
<projectId>:BacklogのプロジェクトID<xxx.backlog.jp>:Backlogドメイン<issueTypeId>:Backlog「電話」種別のID<categoryId_product>:Backlog「商品関連」カテゴリーのID<categoryId_contract>:Backlog「契約関連」カテゴリーのID<categoryId_other>:Backlog「その他」カテゴリーのID<Connectドメイン>:Amazon ConnectインスタンスのURL<ConnectionArn>:作成したEventBridge ConnectionのARN
このステートマシンは以下の処理を行います。
- Base64Decode: KDSから受け取ったBase64エンコードされたデータをデコード
 - ChannelTypeCheck: 通話(VOICE)チャネルのデータのみを処理
 - InquiryTypeCheck: 問い合わせ種別に応じて適切なBacklogカテゴリーを選択
 - ExtractContactData: 問い合わせレコードから必要な情報を抽出
 - FormatContactData: 日時や電話番号を見やすい形式に整形
 - CreateBacklogTicket: BacklogのAPI経由でチケットを作成
 
ステートマシン作成時にIAMロールは自動的に作成されるため、追加の権限設定は不要です。

EventBridge Pipes
KDSからStep Functionsへのデータ連携を行うために、EventBridge Pipesを設定します。
これにより、Amazon Connectからの問い合わせレコードを自動的に処理するパイプラインが完成します。
ソースにはKDSを設定します。

ターゲットにはStep Functionsを設定します。

動作確認
構築したシステムが正常に動作するか、実際に電話をかけて確認してみます。
テストには以下のようなConnectフローを使用します。
このフローでは、IVRで問い合わせカテゴリを選択した後、エージェントに接続される流れになっています。

実際の動作確認手順は以下の通りです
- Amazon Connectの電話番号に発信
 - IVRの案内に従って「商品関連」を選択
 - エージェントと通話
 - 通話終了後、エージェントがアフターコールワークを完了
 
テストの結果、以下のような流れで自動化が正常に動作することを確認できました
- エージェントがアフターコールワークを完了
 - 問い合わせレコードがKDSにストリーミング
 - EventBridge PipesがStep Functionsを起動
 - Step Functionsが通話データを処理
 - Backlogにチケットが自動起票
 
重要ポイントとして、通話データがKDSにストリーミングされるタイミングは、通話終了時ではなくアフターコールワーク完了後です。
作成されたBacklogチケット
アフターコールワーク完了から約1分後、以下のようなチケットがBacklogに自動作成されました。起票者は、APIキーを発行したユーザーになります。

チケットには通話の基本情報が整理されており、「コンタクト詳細URL」をクリックすると、Amazon Connectの管理コンソールに遷移して詳細な通話情報を確認できます。

これにより、Amazon Connectでの通話履歴をBacklogのチケットとして一元管理する仕組みが完成しました。
通話内容の追跡や対応状況の管理が容易になり、チーム間の情報共有も効率化されます。
特定のフローのみBacklog連携したい場合
特定の問い合わせフローだけをBacklogと連携させたい場合、Amazon Connectのフロー設定とStep Functionsの条件分岐を組み合わせることで実現できます。この方法により、必要な通話記録のみをBacklogチケット化する柔軟な運用が可能になります。
対象のConnectフローの最初に、Backlog連携フラグとなるコンタクト属性を設定します。
- キー名:
BacklogIntegration - 値:
true 
また、ステートマシンの処理フローのを以下のように修正します
ChannelTypeCheckステートをBacklogIntegrationEligibilityCheckに変更BacklogIntegrationEligibilityCheckステートに以下の条件分岐を実装- 条件1: チャネルが
VOICEでない場合は処理を終了(NonVoiceChannelEndへ) - 条件2: コンタクト属性
BacklogIntegrationがtrueでない場合は処理を終了(BacklogIntegrationNotEnabledEndへ)- BacklogIntegrationNotEnabledEndは新規作成するステートで、Backlog連携が不要な問い合わせの処理を適切に終了させます。
 
 - デフォルト: 上記条件に一致しない場合(VOICEチャネルかつBacklogIntegration=true)のみ、次の処理(
InquiryTypeCheck)へ進む 
- 条件1: チャネルが
 
この条件分岐により、Backlog連携フラグが設定された通話のみがチケット化されます。

コード(クリックで展開)
{
  "Comment": "通話記録内容をBacklogチケットに起票する",
  "StartAt": "Base64Decode",
  "States": {
    "Base64Decode": {
      "Type": "Pass",
      "Next": "BacklogIntegrationEligibilityCheck",
      "Assign": {
        "decodedData": "{% $base64decode($states.input.data) %}"
      }
    },
    "BacklogIntegrationEligibilityCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "NonVoiceChannelEnd",
          "Condition": "{% $parse($decodedData).Channel != \"VOICE\" %}"
        },
        {
          "Next": "BacklogIntegrationNotEnabledEnd",
          "Condition": "{% $parse($decodedData).Attributes.BacklogIntegration != \"true\" %}"
        }
      ],
      "Default": "InquiryTypeCheck",
      "Assign": {
        "data": "{% $parse($decodedData) %}"
      }
    },
    "BacklogIntegrationNotEnabledEnd": {
      "Type": "Fail"
    },
    "InquiryTypeCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "ExtractContactData",
          "Condition": "{% $data.Attributes.inquiry_type = \"product\" %}",
          "Assign": {
            "categoryId": "<categoryId_product>"
          }
        },
        {
          "Next": "ExtractContactData",
          "Assign": {
            "categoryId": "<categoryId_other>"
          },
          "Condition": "{% $data.Attributes.inquiry_type = \"contract\" %}"
        }
      ],
      "Default": "ExtractContactData",
      "Assign": {
        "categoryId": "<categoryId_contract>"
      }
    },
    "NonVoiceChannelEnd": {
      "Type": "Succeed"
    },
    "ExtractContactData": {
      "Type": "Pass",
      "Assign": {
        "InitialContactId": "{% $data.ContactId %}",
        "InitiationTimestamp": "{% $data.InitiationTimestamp %}",
        "DisconnectTimestamp": "{% $data.DisconnectTimestamp %}",
        "AgentName": "{% $data.Agent.Username %}",
        "CallerPhoneNumber": "{% $data.CustomerEndpoint.Address %}"
      },
      "Next": "FormatContactData"
    },
    "FormatContactData": {
      "Type": "Pass",
      "Assign": {
        "FormattedInitiationTimestamp": "{% $fromMillis($toMillis($InitiationTimestamp), '[Y0001]年[M]月[D]日 [H]時[m01]分[s01]秒', '+0900') %}",
        "FormattedDisconnectTimestamp": "{% $fromMillis($toMillis($DisconnectTimestamp), '[Y0001]年[M]月[D]日 [H]時[m01]分[s01]秒', '+0900') %}",
        "FormattedPhoneNumber": "{% $CallerPhoneNumber = 'anonymous' ? '非通知' : ($substring($CallerPhoneNumber, 0, 3) = '+81' ? '0' & $substring($CallerPhoneNumber, 3) : $CallerPhoneNumber) %}",
        "ContactDetailsURL": "{% 'https://<Connectドメイン>/connect/contact-trace-records/details/' & $InitialContactId & '?tz=Asia/Tokyo' %}"
      },
      "Next": "CreateBacklogTicket"
    },
    "CreateBacklogTicket": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Arguments": {
        "ApiEndpoint": "https://<xxx.backlog.jp>/api/v2/issues",
        "InvocationConfig": {
          "ConnectionArn": "<ConnectionArn>"
        },
        "Headers": {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        "Transform": {
          "RequestBodyEncoding": "URL_ENCODED",
          "RequestEncodingOptions": {
            "ArrayFormat": "INDICES"
          }
        },
        "Method": "POST",
        "RequestBody": {
          "projectId": "<projectId>",
          "summary": "通話記録",
          "description": "{% '|項目|内容|備考|\n|---|---|---|\n|通話開始時間|' & $FormattedInitiationTimestamp & '| |\n|通話終了時間|' & $FormattedDisconnectTimestamp & '| |\n|コンタクトID|' & $InitialContactId & '| [コンタクト詳細URL](' & $ContactDetailsURL & ')|\n|対応者|' & $AgentName & '| |\n|発信者電話番号|' & $FormattedPhoneNumber & '| |' %}",
          "issueTypeId": "<issueTypeId>",
          "categoryId[]": "{% $categoryId %}",
          "priorityId": "3"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "End": true
    }
  },
  "QueryLanguage": "JSONata"
}
この実装により、特定のフローのみをBacklogと連携させる柔軟な運用が可能になります。例えば、重要度の高い問い合わせのみをチケット化したり、特定の部門向けの問い合わせだけを管理したりといった使い分けができます。
注意点と課題
システムを実装する中で、いくつかの課題が明らかになりました。特に問題となるのは、Amazon Connectからの問い合わせレコードの重複配信です。
重複配信の2つのパターン
実際の運用では、以下の2種類の重複配信が発生することがわかりました。
- 問い合わせレコードの更新時の重複配信
 - 同一データの重複配信
 
問い合わせレコードの更新による重複配信
Amazon Connectでは、アフターコールワーク終了後に問い合わせレコードがKDSに送信されますが、その後にレコードが更新されると再度KDSに送信されます。
例えば、自動インタラクションログが有効化されている場合:
- アフターコールワーク完了後、最初の問い合わせレコードがKDSに送信される
 - 自動インタラクションログが生成・保存されると、問い合わせレコードが更新される
 - 更新された問い合わせレコードが再度KDSに送信される
 
これにより、同じコンタクトIDに対して複数回Step Functionsが起動し、重複したチケットが作成されてしまいます。
アフターコールワークから1分後、初回のBacklogチケットが起票され、さらに3分後、2回目のBacklogチケットが起票されることを確認しました。
初回にStep Functionsのdataとして受け取るレコード(クリックで展開)
Recordingsは1つあることがわかります。
{
    "AWSAccountId": "111111111111",
    "AWSContactTraceRecordFormatVersion": "2017-03-10",
    "Agent": {
        "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/agent/104b4727-a2ef-4f12-8daf-6068ce627667",
        "AfterContactWorkDuration": 237,
        "AfterContactWorkEndTimestamp": "2025-04-14T05:48:40Z",
        "AfterContactWorkStartTimestamp": "2025-04-14T05:44:43Z",
        "AgentInteractionDuration": 7,
        "ConnectedToAgentTimestamp": "2025-04-14T05:44:35Z",
        "CustomerHoldDuration": 0,
        "DeviceInfo": {
            "OperatingSystem": "Mac OS X",
            "PlatformName": "Chrome",
            "PlatformVersion": "133"
        },
        "HierarchyGroups": null,
        "LongestHoldDuration": 0,
        "NumberOfHolds": 0,
        "RoutingProfile": {
            "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/routing-profile/3206edca-9180-42e4-a8ae-44727f5d8de6",
            "Name": "cm-hirai"
        },
        "StateTransitions": null,
        "Username": "hirai"
    },
    "AgentConnectionAttempts": 1,
    "AnsweringMachineDetectionStatus": null,
    "Attributes": {
        "Agent": "hirai",
        "inquiry_type": "product"
    },
    "Campaign": {
        "CampaignId": null
    },
    "Channel": "VOICE",
    "ConnectedToSystemTimestamp": "2025-04-14T05:44:15Z",
    "ContactDetails": {},
    "ContactId": "142ef6e4-2555-4767-8c34-6207b399165b",
    "ContactLens": {
        "ConversationalAnalytics": {
            "Configuration": {
                "ChannelConfiguration": {
                    "AnalyticsModes": [
                        "PostContact"
                    ]
                },
                "Enabled": true,
                "LanguageLocale": "ja-JP",
                "RedactionConfiguration": {
                    "Behavior": "Disable",
                    "Entities": null,
                    "MaskMode": null,
                    "Policy": "None"
                },
                "SentimentConfiguration": {
                    "Behavior": "Enable"
                },
                "SummaryConfiguration": null
            }
        }
    },
    "CustomerEndpoint": {
        "Address": "+8150xxxxxxxx",
        "Type": "TELEPHONE_NUMBER"
    },
    "CustomerVoiceActivity": null,
    "DisconnectReason": "CUSTOMER_DISCONNECT",
    "DisconnectTimestamp": "2025-04-14T05:44:43Z",
    "InitialContactId": null,
    "InitiationMethod": "INBOUND",
    "InitiationTimestamp": "2025-04-14T05:44:14Z",
    "InstanceARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
    "LastUpdateTimestamp": "2025-04-14T05:52:49Z",
    "MediaStreams": [
        {
            "Type": "AUDIO"
        }
    ],
    "NextContactId": null,
    "PreviousContactId": null,
    "QualityMetrics": {
        "Agent": {
            "Audio": {
                "PotentialQualityIssues": [],
                "QualityScore": 4.360000133514404
            }
        }
    },
    "Queue": {
        "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/queue/ba8d05d9-27b3-406e-b089-f5707174697e",
        "DequeueTimestamp": "2025-04-14T05:44:35Z",
        "Duration": 6,
        "EnqueueTimestamp": "2025-04-14T05:44:29Z",
        "Name": "cm-hirai"
    },
    "Recording": {
        "DeletionReason": null,
        "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_20250414T05:44_UTC.wav",
        "Status": "AVAILABLE",
        "Type": "AUDIO"
    },
    "Recordings": [
        {
            "DeletionReason": null,
            "FragmentStartNumber": null,
            "FragmentStopNumber": null,
            "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_20250414T05:44_UTC.wav",
            "MediaStreamType": "AUDIO",
            "ParticipantType": null,
            "StartTimestamp": null,
            "Status": "AVAILABLE",
            "StopTimestamp": null,
            "StorageType": "S3"
        },
        {
            "DeletionReason": null,
            "FragmentStartNumber": null,
            "FragmentStopNumber": null,
            "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/AUTOMATED_INTERACTION_LOG/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_f299922f-2e44-49c2-bbb4-b279641078a2_20250414T05:52_UTC.json",
            "MediaStreamType": "AUTOMATED_INTERACTION",
            "ParticipantType": null,
            "StartTimestamp": null,
            "Status": "AVAILABLE",
            "StopTimestamp": null,
            "StorageType": "S3"
        }
    ],
    "References": [],
    "ScheduledTimestamp": null,
    "SegmentAttributes": {
        "connect:Subtype": {
            "ValueInteger": null,
            "ValueList": null,
            "ValueMap": null,
            "ValueString": "connect:Telephony"
        }
    },
    "SystemEndpoint": {
        "Address": "+8150xxxxxxxxxxxx",
        "Type": "TELEPHONE_NUMBER"
    },
    "Tags": {
        "aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
        "aws:connect:systemEndpoint": "+8150xxxxxxxxxxxx"
    },
    "TaskTemplateInfo": null,
    "TransferCompletedTimestamp": null,
    "TransferredToEndpoint": null,
    "VoiceIdResult": null
}
```
レコード更新時Step Functionsのdataとして受け取るレコード(クリックで展開)
Recordingsが2つあることがわかります。AUTOMATED_INTERACTION_LOGタイプが増えています。
{
    "AWSAccountId": "111111111111",
    "AWSContactTraceRecordFormatVersion": "2017-03-10",
    "Agent": {
        "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/agent/104b4727-a2ef-4f12-8daf-6068ce627667",
        "AfterContactWorkDuration": 237,
        "AfterContactWorkEndTimestamp": "2025-04-14T05:48:40Z",
        "AfterContactWorkStartTimestamp": "2025-04-14T05:44:43Z",
        "AgentInteractionDuration": 7,
        "ConnectedToAgentTimestamp": "2025-04-14T05:44:35Z",
        "CustomerHoldDuration": 0,
        "DeviceInfo": {
            "OperatingSystem": "Mac OS X",
            "PlatformName": "Chrome",
            "PlatformVersion": "133"
        },
        "HierarchyGroups": null,
        "LongestHoldDuration": 0,
        "NumberOfHolds": 0,
        "RoutingProfile": {
            "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/routing-profile/3206edca-9180-42e4-a8ae-44727f5d8de6",
            "Name": "cm-hirai"
        },
        "StateTransitions": null,
        "Username": "hirai"
    },
    "AgentConnectionAttempts": 1,
    "AnsweringMachineDetectionStatus": null,
    "Attributes": {
        "Agent": "hirai",
        "inquiry_type": "product"
    },
    "Campaign": {
        "CampaignId": null
    },
    "Channel": "VOICE",
    "ConnectedToSystemTimestamp": "2025-04-14T05:44:15Z",
    "ContactDetails": {},
    "ContactId": "142ef6e4-2555-4767-8c34-6207b399165b",
    "ContactLens": {
        "ConversationalAnalytics": {
            "Configuration": {
                "ChannelConfiguration": {
                    "AnalyticsModes": [
                        "PostContact"
                    ]
                },
                "Enabled": true,
                "LanguageLocale": "ja-JP",
                "RedactionConfiguration": {
                    "Behavior": "Disable",
                    "Entities": null,
                    "MaskMode": null,
                    "Policy": "None"
                },
                "SentimentConfiguration": {
                    "Behavior": "Enable"
                },
                "SummaryConfiguration": null
            }
        }
    },
    "CustomerEndpoint": {
        "Address": "+8150xxxxxxxx",
        "Type": "TELEPHONE_NUMBER"
    },
    "CustomerVoiceActivity": null,
    "DisconnectReason": "CUSTOMER_DISCONNECT",
    "DisconnectTimestamp": "2025-04-14T05:44:43Z",
    "InitialContactId": null,
    "InitiationMethod": "INBOUND",
    "InitiationTimestamp": "2025-04-14T05:44:14Z",
    "InstanceARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
    "LastUpdateTimestamp": "2025-04-14T05:52:49Z",
    "MediaStreams": [
        {
            "Type": "AUDIO"
        }
    ],
    "NextContactId": null,
    "PreviousContactId": null,
    "QualityMetrics": {
        "Agent": {
            "Audio": {
                "PotentialQualityIssues": [],
                "QualityScore": 4.360000133514404
            }
        }
    },
    "Queue": {
        "ARN": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/queue/ba8d05d9-27b3-406e-b089-f5707174697e",
        "DequeueTimestamp": "2025-04-14T05:44:35Z",
        "Duration": 6,
        "EnqueueTimestamp": "2025-04-14T05:44:29Z",
        "Name": "cm-hirai"
    },
    "Recording": {
        "DeletionReason": null,
        "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_20250414T05:44_UTC.wav",
        "Status": "AVAILABLE",
        "Type": "AUDIO"
    },
    "Recordings": [
        {
            "DeletionReason": null,
            "FragmentStartNumber": null,
            "FragmentStopNumber": null,
            "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_20250414T05:44_UTC.wav",
            "MediaStreamType": "AUDIO",
            "ParticipantType": null,
            "StartTimestamp": null,
            "Status": "AVAILABLE",
            "StopTimestamp": null,
            "StorageType": "S3"
        },
        {
            "DeletionReason": null,
            "FragmentStartNumber": null,
            "FragmentStopNumber": null,
            "Location": "amazon-connect-xxxxxxxxxxxx/connect/demo02-classmethod/CallRecordings/AUTOMATED_INTERACTION_LOG/2025/04/14/142ef6e4-2555-4767-8c34-6207b399165b_f299922f-2e44-49c2-bbb4-b279641078a2_20250414T05:52_UTC.json",
            "MediaStreamType": "AUTOMATED_INTERACTION",
            "ParticipantType": null,
            "StartTimestamp": null,
            "Status": "AVAILABLE",
            "StopTimestamp": null,
            "StorageType": "S3"
        }
    ],
    "References": [],
    "ScheduledTimestamp": null,
    "SegmentAttributes": {
        "connect:Subtype": {
            "ValueInteger": null,
            "ValueList": null,
            "ValueMap": null,
            "ValueString": "connect:Telephony"
        }
    },
    "SystemEndpoint": {
        "Address": "+8150xxxxxxx",
        "Type": "TELEPHONE_NUMBER"
    },
    "Tags": {
        "aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
        "aws:connect:systemEndpoint": "+8150xxxxxxx"
    },
    "TaskTemplateInfo": null,
    "TransferCompletedTimestamp": null,
    "TransferredToEndpoint": null,
    "VoiceIdResult": null
}
同一データの重複配信
Amazon Connectの公式ドキュメントには、以下の通り、記載されています。
Amazon Connect は、コンタクトレコードを少なくとも 1 回配信します。問い合わせレコードは、最初の配信後に新しい情報が到着するなど、複数の理由で再度配信される場合があります。たとえば、update-contact-attributes を使用して問い合わせレコードを更新すると、Amazon Connectは新しい問い合わせレコードを配信します。この問い合わせレコードは関連付けられた問い合わせが開始されてから 24 か月間使用可能です。
実際に発生した重複チケット
実際の運用では、以下のように同一コンタクトIDに対して複数のチケットが作成される事象が発生しました。

黒枠が初回配信で、赤枠が問い合わせレコードの更新による2回目の配信。また、赤枠の2つは重複配信
対策案
この問題に対処するためには、以下のような対策が考えられます。
- 
DynamoDBを使用した重複排除
- コンタクトIDをキーとしてDynamoDBに記録し、既に処理済みのコンタクトIDは処理しない
 
 - 
Backlog APIでの重複チェック
- チケット作成前に同一コンタクトIDのチケットが存在するか確認する
 
 
DynamoDBを使用した方法は、AWS内で完結し実装がシンプルなため、DynamoDBを使用した重複排除について、次回試してみます。






