Skip to content

How do I encrypt Amazon Connect call recordings, transcripts, and Contact Lens analytics with my own AWS KMS keys?

8 minute read
Content level: Advanced
1

Connect encrypts customer content at rest by default with a service-managed KMS key, but customers in regulated industries (PCI DSS v4.0, GDPR, HIPAA) typically need full key control — independent rotation, granular IAM, auditable decrypt logs, and the ability to revoke service access. This article walks through configuring Amazon Connect to use a customer-managed KMS key (CMK) for call recordings, chat transcripts, screen recordings, and Contact Lens output, with a deployable AWS CDK example.

Short description

Amazon Connect stores customer content in an Amazon S3 bucket that you associate with the instance via AWS::Connect::InstanceStorageConfig. Each storage configuration takes a ResourceType (such as CALL_RECORDINGS, CHAT_TRANSCRIPTS, SCREEN_RECORDINGS) and an EncryptionConfig that points at your CMK. When you call AssociateInstanceStorageConfig, Connect creates a KMS grant on your CMK with the instance's service-linked role (AWSServiceRoleForAmazonConnect_*) as the grantee — that role, not a generic service principal, is what reads and writes encrypted objects at runtime. Contact Lens conversational analytics output (transcripts and redacted audio) is written under the Analysis/ prefix of the same bucket and inherits the same SSE-KMS encryption.

Resolution

1. Why use a customer-managed CMK

BenefitWhat it gives you
Granular key policyDecide which IAM principals (Connect SLR via grant, compliance auditors, recovery roles) can call kms:Decrypt
CloudTrail auditabilityEvery Encrypt / Decrypt / GenerateDataKey call against your CMK is logged
Independent rotationKMS automatic rotation is configurable (default 365 days, range 90–2560 days); manual rotation any time without re-encrypting objects
RevocationDisable the key, revoke the grant, or schedule deletion to render existing ciphertext unreadable
External Key Store (XKS)Hold key material outside AWS for compliance programs that require it (verify XKS support per downstream Connect feature before relying on it)

The Connect service-linked role needs kms:Decrypt, kms:Encrypt, kms:GenerateDataKey*, and kms:DescribeKey to read and write encrypted objects. Connect creates the grant automatically when you associate a CMK with a storage configuration — the IAM principal calling AssociateInstanceStorageConfig (your CDK/CloudFormation deployment role) must hold kms:CreateGrant on the CMK for that association to succeed.

2. Storage resource types you need to know

A common mistake is assuming CHAT_TRANSCRIPTS covers Contact Lens voice transcripts. It does not. Contact Lens conversational analytics output is written automatically under the Analysis/ prefix of whichever S3 bucket is associated with the instance for CALL_RECORDINGS (voice) or CHAT_TRANSCRIPTS (chat).

S3-backed storage types (these accept EncryptionConfig with your CMK):

ResourceTypeWhat lands here
CALL_RECORDINGSVoice call audio (WAV, stereo); Contact Lens voice transcripts and redacted audio land in the same bucket under Analysis/Voice/ and Analysis/Voice/Redacted/
CHAT_TRANSCRIPTSChat transcripts; Contact Lens chat output lands under Analysis/Chat/ and Analysis/Chat/Redacted/
SCREEN_RECORDINGSAgent screen recordings (when enabled)
ATTACHMENTSFiles exchanged in chat

Streaming-backed storage types (encryption is configured on the destination stream, not on the storage config's EncryptionConfig):

ResourceTypeDestination
CONTACT_TRACE_RECORDSKinesis Data Stream or Kinesis Data Firehose
AGENT_EVENTSKinesis Data Stream
MEDIA_STREAMSKinesis Video Streams (encrypt with KVS-side CMK)
REAL_TIME_CONTACT_ANALYSIS_SEGMENTSKinesis Data Stream

For the streaming targets, set the CMK on the underlying Kinesis/KVS resource — Connect publishes into a stream you've already encrypted. The CDK example below covers the most common S3 targets: CALL_RECORDINGS, CHAT_TRANSCRIPTS, and SCREEN_RECORDINGS.

Email channel: Contact Lens output for Email also lands under Analysis/Email/ if you have email enabled. See the Output file locations page for the current list of supported channels in your Region.

3. Architecture

Architecture

All S3 objects above are encrypted with your CMK via SSE-KMS. For media in transit, Connect uses TLS 1.2+ for signaling and REST APIs, and DTLS-SRTP for the WebRTC media plane.

4. Deploy with AWS CDK (Python)

from aws_cdk import (
    Stack,
    aws_kms as kms,
    aws_s3 as s3,
    aws_connect as connect,
    RemovalPolicy,
)
from constructs import Construct


class ConnectEncryptionStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # CDK's kms.Key creates a default policy granting account root full
        # administrative access. The Connect service-linked role does NOT
        # appear in the key policy directly — it gets access through a grant
        # that AssociateInstanceStorageConfig creates on your behalf.
        connect_key = kms.Key(
            self, "ConnectDataKey",
            alias="alias/connect-data-key",
            enable_key_rotation=True,
            description="Customer-managed CMK for Amazon Connect recordings and transcripts",
        )

        recording_bucket = s3.Bucket(
            self, "ConnectRecordingBucket",
            encryption=s3.BucketEncryption.KMS,
            encryption_key=connect_key,
            bucket_key_enabled=True,
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
            enforce_ssl=True,
            versioned=True,
            removal_policy=RemovalPolicy.RETAIN,
        )

        instance = connect.CfnInstance(
            self, "SecureConnectInstance",
            identity_management_type="CONNECT_MANAGED",
            instance_alias="secure-contact-center",
            attributes=connect.CfnInstance.AttributesProperty(
                inbound_calls=True,
                outbound_calls=True,
                contactflow_logs=True,
                contact_lens=True,
                auto_resolve_best_voices=True,
            ),
        )

        kms_encryption = connect.CfnInstanceStorageConfig.EncryptionConfigProperty(
            encryption_type="KMS",
            key_id=connect_key.key_arn,
        )

        for logical_id, resource_type, prefix in [
            ("CallRecordingsStorage", "CALL_RECORDINGS", "call-recordings/"),
            ("ChatTranscriptsStorage", "CHAT_TRANSCRIPTS", "chat-transcripts/"),
            ("ScreenRecordingsStorage", "SCREEN_RECORDINGS", "screen-recordings/"),
        ]:
            connect.CfnInstanceStorageConfig(
                self, logical_id,
                instance_arn=instance.attr_arn,
                resource_type=resource_type,
                storage_type="S3",
                s3_config=connect.CfnInstanceStorageConfig.S3ConfigProperty(
                    bucket_name=recording_bucket.bucket_name,
                    bucket_prefix=prefix,
                    encryption_config=kms_encryption,
                ),
            )

A few notes that matter in production:

  • Deployer permissions. The principal running cdk deploy must hold kms:CreateGrant on the CMK. The default CDK key policy (account-root access) covers this for most setups; if you tighten the policy, add an explicit allow for the deployment role.
  • bucket_key_enabled=True uses an S3 Bucket Key — a bucket-level intermediate key derived from your CMK — to dramatically reduce KMS API charges on high-call-volume instances. Trade-off: per-object KMS GenerateDataKey/Decrypt events no longer appear individually in CloudTrail (a small number of audit programs require per-object visibility; most PCI/HIPAA audits accept Bucket Key).
  • enforce_ssl=True adds a bucket policy that denies any non-TLS request — required for most compliance audits.
  • Versioning lets you recover from accidental deletion or overwrite. Pair with an S3 Lifecycle rule to expire non-current versions after your retention window.

5. Verify encryption end-to-end

After deployment, place a test call and end it. Then:

  1. S3 console: Connect writes recordings under a path like <bucket>/<bucket-prefix>/connect/<instance-alias>/CallRecordings/<YYYY>/<MM>/<DD>/<contactId>_<timestamp>.wav. Open one of those objects and under Server-side encryption settings confirm AWS Key Management Service key (SSE-KMS) with the key ARN matching your CMK.

  2. CLI:

    aws s3 ls "s3://<bucket-name>/call-recordings/" --recursive | tail -1
    aws s3api head-object \
      --bucket <bucket-name> \
      --key <full-key-from-previous-command>

    Confirm "ServerSideEncryption": "aws:kms" and that "SSEKMSKeyId" matches your CMK ARN. With Bucket Key enabled you'll also see "BucketKeyEnabled": true.

  3. CloudTrail: Filter on event source kms.amazonaws.com and resource ARN = your CMK. You'll see CreateGrant events from the original AssociateInstanceStorageConfig call, and GenerateDataKey calls from the Connect SLR during recording delivery (less frequent with Bucket Key — one per bucket-key rotation rather than per object).

  4. Contact Lens: After post-call analysis completes (typically 1–2 minutes), check s3://<bucket>/Analysis/Voice/<YYYY>/<MM>/<DD>/. The transcript JSON and the redacted audio (under Analysis/Voice/Redacted/) should be there, encrypted with the same CMK.

6. Production considerations

  • Restrict the key policy. The Connect SLR gets access via grant; agents and most analytics users do not need anything in the key policy at all. Compliance auditors should have a separate role with kms:Decrypt only when they need to read the original (un-redacted) transcript. If you tighten the default key policy, keep kms:CreateGrant available to the deployer role and kms:Decrypt available to whoever consumes recordings downstream (Athena workgroups, QuickSight, S3-event-driven Lambdas).

  • Lifecycle policy. S3 Lifecycle rules to transition recordings to S3 Glacier Instant Retrieval after 30–90 days, then expire after your retention window (commonly 7 years for financial services, 6 years for HIPAA).

  • Object Lock. Enable S3 Object Lock in compliance mode if regulators require WORM retention. Object Lock is easiest to enable at bucket creation; since November 2023 it can also be enabled on existing buckets via API by contacting AWS Support — plan ahead either way.

  • Cross-Region replication (CRR). If you need a DR copy, use SSE-KMS replication with a separate CMK in the destination Region. Replication preserves encryption but re-encrypts with the destination key.

  • Voice ID — important deprecation notice. AWS will end support for Amazon Connect Voice ID on May 20, 2026. New deployments should not adopt Voice ID; existing customers should plan migration to a third-party voice biometrics integration before the EOL date. Voice ID's KMS configuration was set on the Voice ID domain (voiceid:CreateDomain with a ServerSideEncryptionConfiguration block) — not via InstanceStorageConfig. The AWS::Connect::InstanceStorageConfig resource has no VOICE_ID ResourceType.

  • Customer Profiles and Connect AI agents. These features encrypt their stores independently of the S3 storage configuration above:

    • Customer Profiles: configure a CMK at domain creation, optionally a per-Object-Type CMK. Once Data Vault is enabled, you cannot rotate the domain or Object Type CMK — plan key choice carefully up-front.
    • Connect AI agents (formerly Wisdom / Amazon Q in Connect): supports BYOK for knowledge documents and call transcripts; search indices are always encrypted with an AWS-owned key.
  • Cases. Connect Cases encrypts customer-provided data with AWS-owned keys only — there is currently no BYOK option, so Cases is out of scope for a CMK strategy.

Related information