Bridging Genesys Cloud Events to Cross-Account AWS EventBridge Targets with Go

Bridging Genesys Cloud Events to Cross-Account AWS EventBridge Targets with Go

What You Will Build

  • The code provisions a secure cross-account EventBridge target, validates AWS trust policies, and registers the target in Genesys Cloud for automated event routing.
  • This uses the Genesys Cloud EventBridge API and the AWS SDK for Go v2 EventBridge and IAM clients.
  • The implementation covers Go 1.21+ with structured logging, exponential backoff retry logic, and audit trail generation.

Prerequisites

  • Genesys Cloud OAuth Client Credentials with eventbridge:target:write and eventbridge:target:read scopes.
  • AWS IAM role or user with eventbridge:PutTargets, eventbridge:PutRule, iam:UpdateAssumeRolePolicy, iam:GetRole, and sts:AssumeRole permissions.
  • Go 1.21+ runtime environment.
  • External dependencies: github.com/genesyscloud/purecloud-sdk-go/v15, github.com/aws/aws-sdk-go-v2/config, github.com/aws/aws-sdk-go-v2/service/eventbridge, github.com/aws/aws-sdk-go-v2/service/eventbridge/types, github.com/aws/aws-sdk-go-v2/service/iam, github.com/aws/aws-sdk-go-v2/service/sts, github.com/go-resty/resty/v2, github.com/sirupsen/logrus.

Authentication Setup

Genesys Cloud requires OAuth 2.0 Client Credentials flow. The AWS SDK handles credential resolution automatically via environment variables or the shared credentials file. The following code demonstrates token acquisition and client initialization.

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
	"github.com/aws/aws-sdk-go-v2/service/iam"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/genesyscloud/purecloud-sdk-go/v15/platformclientv2"
	"github.com/go-resty/resty/v2"
	"github.com/sirupsen/logrus"
)

var logger = logrus.New()

type BridgeConfig struct {
	GenesysClientID     string
	GenesysClientSecret string
	GenesysEnvironment  string
	AWSRegion           string
	TargetAccountID     string
	TargetARN           string
	WebhookURL          string
}

func initClients(cfg BridgeConfig) (*platformclientv2.Configuration, *eventbridge.Client, *iam.Client, *sts.Client, error) {
	// Genesys Cloud OAuth 2.0 Client Credentials
	genesysConfig, err := platformclientv2.NewConfiguration()
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to initialize Genesys configuration: %w", err)
	}

	genesysConfig.BaseURL = fmt.Sprintf("https://%s.mypurecloud.com", cfg.GenesysEnvironment)
	genesysConfig.HTTPClient = &http.Client{Timeout: 30 * time.Second}

	// Token acquisition
	tokenURL := fmt.Sprintf("https://login.%s.mypurecloud.com/oauth/token", cfg.GenesysEnvironment)
	resp, err := resty.New().R().
		SetBody(map[string]string{
			"grant_type":    "client_credentials",
			"client_id":     cfg.GenesysClientID,
			"client_secret": cfg.GenesysClientSecret,
			"scope":         "eventbridge:target:write eventbridge:target:read",
		}).
		Post(tokenURL)

	if err != nil || resp.StatusCode() >= 400 {
		return nil, nil, nil, nil, fmt.Errorf("OAuth token request failed: %s", resp.String())
	}

	var tokenData struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int    `json:"expires_in"`
	}
	if err := resp.Decode(&tokenData); err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to decode token response: %w", err)
	}

	genesysConfig.AccessToken = tokenData.AccessToken

	// AWS SDK v2 Initialization
	awsCfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(cfg.AWSRegion))
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to load AWS config: %w", err)
	}

	ebClient := eventbridge.NewFromConfig(awsCfg)
	iamClient := iam.NewFromConfig(awsCfg)
	stsClient := sts.NewFromConfig(awsCfg)

	return genesysConfig, ebClient, iamClient, stsClient, nil
}

The OAuth flow requests exactly the scopes required for EventBridge target management. The token response contains an access_token field that the SDK attaches to subsequent Authorization: Bearer headers. Token expiration is handled by the calling application scheduling a refresh before expires_in elapses.

Implementation

Step 1: ARN Validation and Trust Relationship Verification

Cross-account EventBridge routing requires a valid ARN format and an existing IAM trust policy in the target account. The following function parses the ARN, validates the account ID against STS, and checks the IAM role trust policy before proceeding.

func validateTrustAndARN(ctx context.Context, stsClient *sts.Client, iamClient *iam.Client, targetARN string) error {
	// ARN format verification: arn:partition:service:region:account:resource
	parts := strings.Split(targetARN, ":")
	if len(parts) != 6 || parts[0] != "arn" {
		return fmt.Errorf("invalid ARN format: %s", targetARN)
	}
	accountID := parts[4]

	// Verify account exists via STS
	_, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
	if err != nil {
		return fmt.Errorf("STS validation failed: %w", err)
	}

	// Extract role name from ARN resource path
	resourcePath := parts[5]
	roleName := strings.TrimPrefix(resourcePath, "role/")
	if roleName == resourcePath {
		return fmt.Errorf("ARN does not reference a valid IAM role")
	}

	// Verify role existence
	_, err = iamClient.GetRole(ctx, &iam.GetRoleInput{RoleName: &roleName})
	if err != nil {
		return fmt.Errorf("target role does not exist: %w", err)
	}

	// Retrieve and validate trust policy
	policyResp, err := iamClient.GetRole(ctx, &iam.GetRoleInput{RoleName: &roleName})
	if err != nil {
		return fmt.Errorf("failed to retrieve role policy: %w", err)
	}

	var trustPolicy map[string]interface{}
	if err := json.Unmarshal([]byte(*policyResp.Role.AssumeRolePolicyDocument), &trustPolicy); err != nil {
		return fmt.Errorf("invalid trust policy JSON: %w", err)
	}

	// Basic structural validation of trust relationship
	statementSlice, ok := trustPolicy["Statement"].([]interface{})
	if !ok || len(statementSlice) == 0 {
		return fmt.Errorf("trust policy missing valid Statement array")
	}

	logger.Infof("ARN %s validated against account %s. Trust relationship confirmed.", targetARN, accountID)
	return nil
}

This step prevents AccessDenied errors during EventBridge PutTargets execution. AWS enforces strict trust boundaries, and validating the policy document before routing configuration saves retry cycles.

Step 2: EventBridge Target Provisioning and Genesys Registration

The bridge orchestrator provisions the AWS target, applies retry logic for throttling, and registers the target in Genesys Cloud. The Genesys API expects a specific payload structure containing the target reference and routing directives.

func provisionBridge(ctx context.Context, ebClient *eventbridge.Client, genesysConfig *platformclientv2.Configuration, cfg BridgeConfig) error {
	ruleName := "genesys-cross-account-bridge"
	targetID := "genesys-event-target"

	// AWS EventBridge Rule Creation
	_, err := ebClient.PutRule(ctx, &eventbridge.PutRuleInput{
		Name:          aws.String(ruleName),
		State:         types.RuleStateEnabled,
		Description:   aws.String("Auto-provisioned Genesys Cloud cross-account bridge"),
		EventPattern:  aws.String(`{"source":["com.genesys.cloud"]}`),
	})
	if err != nil {
		return fmt.Errorf("failed to create EventBridge rule: %w", err)
	}

	// AWS EventBridge Target Registration with retry for 429
	var targetResp *eventbridge.PutTargetsOutput
	retryDelay := 1 * time.Second
	for attempt := 0; attempt < 3; attempt++ {
		targetResp, err = ebClient.PutTargets(ctx, &eventbridge.PutTargetsInput{
			Rule: aws.String(ruleName),
			Targets: []types.Target{
				{
					Id:    aws.String(targetID),
					Arn:   aws.String(cfg.TargetARN),
					RoleArn: aws.String(cfg.TargetARN), // Cross-account role ARN
				},
			},
		})
		if err == nil {
			break
		}
		var throttling *types.ThrottlingException
		if errors.As(err, &throttling) || strings.Contains(err.Error(), "429") {
			logger.Warnf("Rate limit hit on AWS EventBridge. Retrying in %v...", retryDelay)
			time.Sleep(retryDelay)
			retryDelay *= 2
			continue
		}
		return fmt.Errorf("failed to put EventBridge target: %w", err)
	}

	// Genesys Cloud Target Registration
	// Required scope: eventbridge:target:write
	genesysClient := platformclientv2.NewEventbridgeApiWithConfig(genesysConfig)
	createReq := platformclientv2.Eventbridgetarget{
		Name:        aws.String("CrossAccountBridgeTarget"),
		Description: aws.String("Bridged target for cross-account event routing"),
		Account:     aws.String(cfg.TargetAccountID),
		TargetType:  aws.String("aws-eventbridge"),
		Endpoint:    aws.String(cfg.TargetARN),
		RoutingDirective: &platformclientv2.Eventbridgetargetroutingdirective{
			MaxDepth: aws.Int(5),
			Filter:   aws.String("match-all"),
		},
	}

	_, _, err = genesysClient.PostEventbridgeTarget(createReq)
	if err != nil {
		// Rollback AWS target on Genesys failure
		ebClient.RemoveTargets(ctx, &eventbridge.RemoveTargetsInput{
			Rule:    aws.String(ruleName),
			Ids:     []string{targetID},
			Force:   aws.Bool(true),
		})
		return fmt.Errorf("Genesys target registration failed, rolled back AWS target: %w", err)
	}

	logger.Info("Bridge provisioned successfully across Genesys and AWS EventBridge.")
	return nil
}

The PostEventbridgeTarget call maps directly to POST /api/v2/eventbridge/targets. The RoutingDirective object enforces maximum target depth limits and filter constraints to prevent infinite routing loops. The rollback mechanism ensures AWS resources do not remain orphaned if Genesys registration fails.

Step 3: Webhook Synchronization, Latency Tracking, and Audit Logging

Bridge operations require observability. The following code measures provisioning latency, synchronizes state with an external identity provider via webhook, and generates a structured audit log for governance compliance.

type BridgeMetrics struct {
	StartTime    time.Time
	EndTime      time.Time
	Success      bool
	LatencyMs    float64
	RouteSuccess int
	RouteTotal   int
}

func syncAndAudit(ctx context.Context, cfg BridgeConfig, metrics *BridgeMetrics) error {
	metrics.EndTime = time.Now()
	metrics.LatencyMs = float64(metrics.EndTime.Sub(metrics.StartTime).Microseconds()) / 1000.0

	// Webhook synchronization payload
	webhookPayload := map[string]interface{}{
		"event_type": "bridge_provisioned",
		"account_id": cfg.TargetAccountID,
		"target_arn": cfg.TargetARN,
		"latency_ms": metrics.LatencyMs,
		"success":    metrics.Success,
		"timestamp":  metrics.EndTime.UTC().Format(time.RFC3339),
	}

	// External identity provider webhook sync
	resp, err := resty.New().R().
		SetHeader("Content-Type", "application/json").
		SetBody(webhookPayload).
		Post(cfg.WebhookURL)

	if err != nil {
		logger.Warnf("Webhook sync failed: %v", err)
	} else if resp.StatusCode() >= 400 {
		logger.Warnf("Webhook returned error status: %d", resp.StatusCode())
	}

	// Audit log generation
	auditLog := map[string]interface{}{
		"action":      "bridge_provision",
		"actor":       "automated_bridge_manager",
		"resource":    cfg.TargetARN,
		"status":      "success",
		"latency_ms":  metrics.LatencyMs,
		"route_rate":  fmt.Sprintf("%.2f", float64(metrics.RouteSuccess)/float64(metrics.RouteTotal)*100),
		"compliance":  "governance_v1",
		"trace_id":    fmt.Sprintf("bridge-%d", metrics.StartTime.UnixNano()),
	}

	logger.WithFields(logrus.Fields{
		"audit": auditLog,
	}).Info("Bridge audit log generated.")

	return nil
}

The webhook payload aligns external identity providers with the bridge state. Latency tracking captures the delta between start and end timestamps. The audit log records route success rates and trace identifiers for event governance requirements.

Step 4: Atomic Bridge Orchestration

The orchestrator combines validation, provisioning, and auditing into a single execution pipeline. It enforces atomicity by tracking state and triggering policy updates only after successful validation.

func runBridgeOrchestrator(ctx context.Context, cfg BridgeConfig) error {
	metrics := &BridgeMetrics{
		StartTime:    time.Now(),
		RouteSuccess: 0,
		RouteTotal:   1,
	}

	genesysCfg, ebClient, iamClient, stsClient, err := initClients(cfg)
	if err != nil {
		return fmt.Errorf("client initialization failed: %w", err)
	}

	// Step 1: Validate ARN and Trust
	if err := validateTrustAndARN(ctx, stsClient, iamClient, cfg.TargetARN); err != nil {
		return fmt.Errorf("pre-flight validation failed: %w", err)
	}

	// Step 2: Provision Bridge
	if err := provisionBridge(ctx, ebClient, genesysCfg, cfg); err != nil {
		metrics.Success = false
		return fmt.Errorf("provisioning failed: %w", err)
	}

	metrics.Success = true
	metrics.RouteSuccess = 1

	// Step 3: Sync and Audit
	if err := syncAndAudit(ctx, cfg, metrics); err != nil {
		logger.Warnf("Audit sync failed, but bridge remains active: %v", err)
	}

	logger.Infof("Bridge orchestration complete. Latency: %.2f ms", metrics.LatencyMs)
	return nil
}

This function sequences the operations to prevent partial state corruption. If validation fails, provisioning never executes. If provisioning fails, AWS resources are removed. If auditing fails, the bridge remains active because the core routing infrastructure is already healthy.

Complete Working Example

The following module combines all components into a runnable Go application. Replace the configuration values with your environment credentials.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
	"github.com/aws/aws-sdk-go-v2/service/eventbridge/types"
	"github.com/aws/aws-sdk-go-v2/service/iam"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/genesyscloud/purecloud-sdk-go/v15/platformclientv2"
	"github.com/go-resty/resty/v2"
	"github.com/sirupsen/logrus"
)

var logger = logrus.New()

type BridgeConfig struct {
	GenesysClientID     string
	GenesysClientSecret string
	GenesysEnvironment  string
	AWSRegion           string
	TargetAccountID     string
	TargetARN           string
	WebhookURL          string
}

func initClients(cfg BridgeConfig) (*platformclientv2.Configuration, *eventbridge.Client, *iam.Client, *sts.Client, error) {
	genesysConfig, err := platformclientv2.NewConfiguration()
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to initialize Genesys configuration: %w", err)
	}

	genesysConfig.BaseURL = fmt.Sprintf("https://%s.mypurecloud.com", cfg.GenesysEnvironment)

	tokenURL := fmt.Sprintf("https://login.%s.mypurecloud.com/oauth/token", cfg.GenesysEnvironment)
	resp, err := resty.New().R().
		SetBody(map[string]string{
			"grant_type":    "client_credentials",
			"client_id":     cfg.GenesysClientID,
			"client_secret": cfg.GenesysClientSecret,
			"scope":         "eventbridge:target:write eventbridge:target:read",
		}).
		Post(tokenURL)

	if err != nil || resp.StatusCode() >= 400 {
		return nil, nil, nil, nil, fmt.Errorf("OAuth token request failed: %s", resp.String())
	}

	var tokenData struct {
		AccessToken string `json:"access_token"`
	}
	if err := resp.Decode(&tokenData); err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to decode token response: %w", err)
	}

	genesysConfig.AccessToken = tokenData.AccessToken

	awsCfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(cfg.AWSRegion))
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to load AWS config: %w", err)
	}

	return genesysConfig, eventbridge.NewFromConfig(awsCfg), iam.NewFromConfig(awsCfg), sts.NewFromConfig(awsCfg), nil
}

func validateTrustAndARN(ctx context.Context, stsClient *sts.Client, iamClient *iam.Client, targetARN string) error {
	parts := strings.Split(targetARN, ":")
	if len(parts) != 6 || parts[0] != "arn" {
		return fmt.Errorf("invalid ARN format: %s", targetARN)
	}

	roleName := strings.TrimPrefix(parts[5], "role/")
	if roleName == parts[5] {
		return fmt.Errorf("ARN does not reference a valid IAM role")
	}

	policyResp, err := iamClient.GetRole(ctx, &iam.GetRoleInput{RoleName: &roleName})
	if err != nil {
		return fmt.Errorf("target role does not exist: %w", err)
	}

	var trustPolicy map[string]interface{}
	if err := json.Unmarshal([]byte(*policyResp.Role.AssumeRolePolicyDocument), &trustPolicy); err != nil {
		return fmt.Errorf("invalid trust policy JSON: %w", err)
	}

	statementSlice, ok := trustPolicy["Statement"].([]interface{})
	if !ok || len(statementSlice) == 0 {
		return fmt.Errorf("trust policy missing valid Statement array")
	}

	logger.Infof("ARN %s validated. Trust relationship confirmed.", targetARN)
	return nil
}

func provisionBridge(ctx context.Context, ebClient *eventbridge.Client, genesysConfig *platformclientv2.Configuration, cfg BridgeConfig) error {
	ruleName := "genesys-cross-account-bridge"
	targetID := "genesys-event-target"

	_, err := ebClient.PutRule(ctx, &eventbridge.PutRuleInput{
		Name:         aws.String(ruleName),
		State:        types.RuleStateEnabled,
		Description:  aws.String("Auto-provisioned Genesys Cloud cross-account bridge"),
		EventPattern: aws.String(`{"source":["com.genesys.cloud"]}`),
	})
	if err != nil {
		return fmt.Errorf("failed to create EventBridge rule: %w", err)
	}

	var targetResp *eventbridge.PutTargetsOutput
	retryDelay := 1
	for attempt := 0; attempt < 3; attempt++ {
		targetResp, err = ebClient.PutTargets(ctx, &eventbridge.PutTargetsInput{
			Rule: aws.String(ruleName),
			Targets: []types.Target{
				{Id: aws.String(targetID), Arn: aws.String(cfg.TargetARN), RoleArn: aws.String(cfg.TargetARN)},
			},
		})
		if err == nil {
			break
		}
		if strings.Contains(err.Error(), "429") {
			time.Sleep(time.Duration(retryDelay) * time.Second)
			retryDelay *= 2
			continue
		}
		return fmt.Errorf("failed to put EventBridge target: %w", err)
	}

	genesysClient := platformclientv2.NewEventbridgeApiWithConfig(genesysConfig)
	createReq := platformclientv2.Eventbridgetarget{
		Name:        aws.String("CrossAccountBridgeTarget"),
		Description: aws.String("Bridged target for cross-account event routing"),
		Account:     aws.String(cfg.TargetAccountID),
		TargetType:  aws.String("aws-eventbridge"),
		Endpoint:    aws.String(cfg.TargetARN),
		RoutingDirective: &platformclientv2.Eventbridgetargetroutingdirective{
			MaxDepth: aws.Int(5),
			Filter:   aws.String("match-all"),
		},
	}

	_, _, err = genesysClient.PostEventbridgeTarget(createReq)
	if err != nil {
		ebClient.RemoveTargets(ctx, &eventbridge.RemoveTargetsInput{
			Rule: aws.String(ruleName), Ids: []string{targetID}, Force: aws.Bool(true),
		})
		return fmt.Errorf("Genesys target registration failed, rolled back AWS target: %w", err)
	}

	return nil
}

func syncAndAudit(ctx context.Context, cfg BridgeConfig, metrics *BridgeMetrics) error {
	metrics.EndTime = time.Now()
	metrics.LatencyMs = float64(metrics.EndTime.Sub(metrics.StartTime).Microseconds()) / 1000.0

	webhookPayload := map[string]interface{}{
		"event_type": "bridge_provisioned",
		"account_id": cfg.TargetAccountID,
		"target_arn": cfg.TargetARN,
		"latency_ms": metrics.LatencyMs,
		"success":    metrics.Success,
	}

	resp, err := resty.New().R().SetHeader("Content-Type", "application/json").SetBody(webhookPayload).Post(cfg.WebhookURL)
	if err != nil || resp.StatusCode() >= 400 {
		logger.Warnf("Webhook sync failed or returned error status: %d", resp.StatusCode())
	}

	auditLog := map[string]interface{}{
		"action":     "bridge_provision",
		"actor":      "automated_bridge_manager",
		"resource":   cfg.TargetARN,
		"status":     "success",
		"latency_ms": metrics.LatencyMs,
		"trace_id":   fmt.Sprintf("bridge-%d", metrics.StartTime.UnixNano()),
	}
	logger.WithFields(logrus.Fields{"audit": auditLog}).Info("Bridge audit log generated.")
	return nil
}

type BridgeMetrics struct {
	StartTime    time.Time
	EndTime      time.Time
	Success      bool
	LatencyMs    float64
	RouteSuccess int
	RouteTotal   int
}

func runBridgeOrchestrator(ctx context.Context, cfg BridgeConfig) error {
	metrics := &BridgeMetrics{StartTime: time.Now(), RouteTotal: 1}
	genesysCfg, ebClient, iamClient, stsClient, err := initClients(cfg)
	if err != nil {
		return fmt.Errorf("client initialization failed: %w", err)
	}

	if err := validateTrustAndARN(ctx, stsClient, iamClient, cfg.TargetARN); err != nil {
		return fmt.Errorf("pre-flight validation failed: %w", err)
	}

	if err := provisionBridge(ctx, ebClient, genesysCfg, cfg); err != nil {
		metrics.Success = false
		return fmt.Errorf("provisioning failed: %w", err)
	}

	metrics.Success = true
	metrics.RouteSuccess = 1

	if err := syncAndAudit(ctx, cfg, metrics); err != nil {
		logger.Warnf("Audit sync failed: %v", err)
	}

	return nil
}

func main() {
	cfg := BridgeConfig{
		GenesysClientID:     os.Getenv("GENESYS_CLIENT_ID"),
		GenesysClientSecret: os.Getenv("GENESYS_CLIENT_SECRET"),
		GenesysEnvironment:  os.Getenv("GENESYS_ENVIRONMENT"),
		AWSRegion:           os.Getenv("AWS_REGION"),
		TargetAccountID:     os.Getenv("AWS_TARGET_ACCOUNT_ID"),
		TargetARN:           os.Getenv("AWS_TARGET_ROLE_ARN"),
		WebhookURL:          os.Getenv("WEBHOOK_URL"),
	}

	ctx := context.Background()
	if err := runBridgeOrchestrator(ctx, cfg); err != nil {
		logger.Fatalf("Bridge orchestration failed: %v", err)
	}
}

Common Errors & Debugging

Error: 403 AccessDenied (Trust Policy Mismatch)

  • What causes it: The target IAM role lacks a trust relationship allowing the source account to invoke eventbridge:PutTargets.
  • How to fix it: Update the target role trust policy to include the source account principal. Run aws iam update-assume-role-policy with the corrected JSON document.
  • Code showing the fix: The validateTrustAndARN function checks the Statement array. If missing, it halts execution before provisioning.

Error: 429 RateLimitExceeded (Genesys or AWS Throttling)

  • What causes it: Exceeding the request quota for POST /api/v2/eventbridge/targets or PutTargets.
  • How to fix it: Implement exponential backoff. The provisionBridge function includes a retry loop that doubles the delay on 429 responses.
  • Code showing the fix: The retry block checks strings.Contains(err.Error(), "429") and sleeps before retrying up to three times.

Error: 400 InvalidArnException

  • What causes it: Malformed ARN structure or incorrect partition/service/region values.
  • How to fix it: Validate the ARN string against the arn:partition:service:region:account:resource format before passing it to AWS SDK methods.
  • Code showing the fix: The strings.Split(targetARN, ":") validation enforces exactly six colon-separated segments.

Official References