Skip to content

Post-View Post-Click Attribution

Post-View Post-Click Attribution measures conversions after ad exposure, accounting for both immediate click responses and delayed decisions. It captures journeys with extended consideration, multiple exposures, and complex decision-making that single-interaction models miss.

Dual Attribution Framework

Post-view post-click attribution treats both passive exposure and active engagement as conversion drivers.

Components: - Post-view conversions: Actions after seeing but not clicking ads - Post-click conversions: Actions after directly clicking ads - Combined influence: Cumulative advertising impact - Extended attribution windows: Capture delayed decisions

Practical example:

Customer journey: Display Ad View → 3 days → Email Click → 2 days → Social Ad View → 1 day → Direct Purchase

Result: - Display Ad View: 30% credit (initial exposure) - Email Click: 50% credit (active engagement) - Social Ad View: 20% credit (final reinforcement) - Total: 100% across multiple touchpoints

Implementation

Tracking infrastructure:

// Post-view post-click attribution system
class PostViewPostClickAttribution {
    constructor() {
        this.viewEvents = [];
        this.clickEvents = [];
        this.viewWindow = 30; // days
        this.clickWindow = 30; // days
    }

    trackAdView(userId, viewData) {
        const impression = {
            user_id: userId,
            timestamp: Date.now(),
            campaign_id: viewData.campaign_id,
            creative_id: viewData.creative_id,
            placement: viewData.placement,
            source: viewData.source,
            medium: 'display',
            interaction_type: 'view'
        };

        this.storeInteraction(impression);
        return impression;
    }

    trackAdClick(userId, clickData) {
        const click = {
            user_id: userId,
            timestamp: Date.now(),
            campaign_id: clickData.campaign_id,
            creative_id: clickData.creative_id,
            source: clickData.source,
            medium: clickData.medium,
            interaction_type: 'click',
            click_id: this.generateClickId()
        };

        this.storeInteraction(click);
        return click;
    }

    attributeConversion(userId, conversionData) {
        const viewInteractions = this.getViewsInWindow(userId);
        const clickInteractions = this.getClicksInWindow(userId);

        return this.calculateCombinedAttribution(
            viewInteractions,
            clickInteractions,
            conversionData
        );
    }

    calculateCombinedAttribution(views, clicks, conversion) {
        const allInteractions = [...views, ...clicks]
            .sort((a, b) => a.timestamp - b.timestamp);

        const attribution = {
            total_value: conversion.value,
            attributed_interactions: [],
            view_contribution: 0,
            click_contribution: 0
        };

        // Apply time-decay and interaction-type weighting
        allInteractions.forEach(interaction => {
            const timeDecay = this.calculateTimeDecay(
                interaction.timestamp,
                conversion.timestamp
            );

            const interactionWeight = interaction.interaction_type === 'click' ? 0.7 : 0.3;
            const credit = timeDecay * interactionWeight;

            attribution.attributed_interactions.push({
                ...interaction,
                credit_percentage: credit,
                attributed_value: conversion.value * credit
            });

            if (interaction.interaction_type === 'view') {
                attribution.view_contribution += credit;
            } else {
                attribution.click_contribution += credit;
            }
        });

        return attribution;
    }
}

Cross-platform tracking integration:

// Unified post-view post-click tracking
function initializeUnifiedTracking() {
    // Google Ads view-through conversion tracking
    gtag('config', 'AW-CONVERSION_ID', {
        'allow_google_signals': true,
        'enhanced_conversions': true
    });

    // Facebook view and click tracking
    fbq('init', 'PIXEL_ID', {
        'external_id': getUserId()
    });

    // Custom cross-platform attribution
    const attributionTracker = new PostViewPostClickAttribution();

    // Track display ad impressions
    document.addEventListener('adImpression', (event) => {
        attributionTracker.trackAdView(getUserId(), event.detail);
    });

    // Track ad clicks
    document.addEventListener('adClick', (event) => {
        attributionTracker.trackAdClick(getUserId(), event.detail);
    });

    // Track conversions
    document.addEventListener('conversion', (event) => {
        const attribution = attributionTracker.attributeConversion(
            getUserId(),
            event.detail
        );

        sendAttributionData(attribution);
    });
}

Advantages

Complete Journey Visibility

Touchpoint coverage:

Our research shows clear improvements in customer behavior understanding:

Journey complexity:

graph TD
    A[Display Ad View] --> B[3-day delay]
    B --> C[Search Query]
    C --> D[Email Click]
    D --> E[2-day delay]
    E --> F[Retargeting View]
    F --> G[Social Media View]
    G --> H[Direct Purchase]

    A -->|View Credit: 25%| I[Attribution Result]
    D -->|Click Credit: 45%| I
    F -->|View Credit: 15%| I
    G -->|View Credit: 15%| I

Behavior insights: - Average 4.2 touchpoints before conversion - 68% include both views and clicks - 23% involve multi-day consideration - View interactions raise conversion probability by 35%

True Marketing Impact

Holistic ROI:

Post-view post-click attribution shows the complete value of marketing investments.

Channel contribution: | Channel | View Conversions | Click Conversions | Combined Value | True ROI | |---------|------------------|-------------------|----------------|----------| | Display Advertising | 450 | 150 | $125,000 | 285% | | Social Media | 320 | 280 | $95,000 | 190% | | Video Advertising | 680 | 95 | $88,000 | 220% | | Email Marketing | 180 | 520 | $105,000 | 525% |

Optimization insights:

# True channel value calculation
def calculate_true_channel_value():
    channels = get_marketing_channels()

    for channel in channels:
        view_value = calculate_view_attribution_value(channel)
        click_value = calculate_click_attribution_value(channel)

        total_value = view_value + click_value
        total_cost = channel['spend']

        true_roi = (total_value - total_cost) / total_cost

        # Compare with click-only attribution
        click_only_roi = (click_value - total_cost) / total_cost

        uplift = true_roi - click_only_roi

        print(f"""
        Channel: {channel['name']}
        Click-only ROI: {click_only_roi:.1%}
        True ROI: {true_roi:.1%}
        Attribution Uplift: {uplift:.1%}
        """)

Brand Building Validation

Upper-funnel impact:

Post-view post-click attribution validates awareness campaigns.

Brand campaign performance: - 43% of conversions include view-through touchpoints - Brand awareness campaigns show 2.3x higher assisted conversion rates - Video advertising shows 65% view-through contribution - Display campaigns reveal 45% hidden conversion influence

Long-term value:

Customer Lifetime Value by Attribution Path:
Click-only customers: $340 average CLV
View-only customers: $425 average CLV
Combined (view + click): $580 average CLV

Insight: Customers with view exposure show higher long-term value

Challenges and Solutions

Attribution Window Complexity

Managing multiple timeframes:

// Sophisticated attribution window management
class AttributionWindowManager {
    constructor() {
        this.windowConfigurations = {
            'display': { view: 30, click: 90 },
            'video': { view: 7, click: 30 },
            'social': { view: 7, click: 30 },
            'search': { view: 1, click: 90 },
            'email': { view: 3, click: 30 }
        };
    }

    isInteractionValid(interaction, conversionTimestamp) {
        const config = this.windowConfigurations[interaction.channel];
        const daysSince = this.daysBetween(
            interaction.timestamp,
            conversionTimestamp
        );

        if (interaction.type === 'view') {
            return daysSince <= config.view;
        } else {
            return daysSince <= config.click;
        }
    }

    getValidInteractions(userId, conversionData) {
        const allInteractions = this.getAllInteractions(userId);

        return allInteractions.filter(interaction => 
            this.isInteractionValid(interaction, conversionData.timestamp)
        );
    }
}

Cross-device attribution:

# Cross-device post-view post-click attribution
class CrossDeviceAttribution:
    def __init__(self):
        self.identity_graph = IdentityGraph()

    def link_interactions_across_devices(self, user_id):
        # Get all device identities for user
        device_ids = self.identity_graph.get_linked_devices(user_id)

        all_interactions = []
        for device_id in device_ids:
            device_interactions = self.get_device_interactions(device_id)
            all_interactions.extend(device_interactions)

        # Sort by timestamp for attribution calculation
        return sorted(all_interactions, key=lambda x: x['timestamp'])

    def attribute_cross_device_conversion(self, user_id, conversion):
        interactions = self.link_interactions_across_devices(user_id)

        # Apply post-view post-click attribution logic
        return self.calculate_attribution(interactions, conversion)

Privacy Compliance

Privacy-first approach:

// Privacy-compliant attribution system
class PrivacyCompliantAttribution {
    constructor() {
        this.consentManager = new ConsentManager();
        this.attributionData = new Map();
    }

    trackWithConsent(userId, interactionData) {
        const consent = this.consentManager.getConsent(userId);

        if (consent.analytics && consent.advertising) {
            // Full attribution tracking
            return this.trackFullAttribution(userId, interactionData);
        } else if (consent.analytics) {
            // Anonymized attribution tracking
            return this.trackAnonymizedAttribution(interactionData);
        } else {
            // No tracking
            return null;
        }
    }

    trackAnonymizedAttribution(interactionData) {
        // Remove personally identifiable information
        const anonymized = {
            timestamp: interactionData.timestamp,
            campaign_id: interactionData.campaign_id,
            creative_id: interactionData.creative_id,
            interaction_type: interactionData.interaction_type,
            anonymous_id: this.generateAnonymousId()
        };

        this.storeAnonymizedInteraction(anonymized);
        return anonymized;
    }
}

Data Quality

Confidence scoring:

# Attribution confidence calculation
def calculate_attribution_confidence(attribution_result):
    confidence_factors = {
        'interaction_count': min(len(attribution_result['interactions']) / 5, 1.0),
        'time_proximity': calculate_time_proximity_score(attribution_result),
        'interaction_quality': calculate_interaction_quality_score(attribution_result),
        'cross_validation': validate_against_other_models(attribution_result)
    }

    # Weighted confidence score
    weights = {
        'interaction_count': 0.25,
        'time_proximity': 0.30,
        'interaction_quality': 0.25,
        'cross_validation': 0.20
    }

    confidence_score = sum(
        confidence_factors[factor] * weights[factor]
        for factor in confidence_factors
    )

    return {
        'confidence_score': confidence_score,
        'confidence_level': get_confidence_level(confidence_score),
        'factors': confidence_factors
    }

Advanced Strategies

ML Enhancement

Predictive attribution modeling:

# ML-enhanced post-view post-click attribution
import tensorflow as tf
from sklearn.ensemble import RandomForestRegressor

class MLAttributionModel:
    def __init__(self):
        self.model = self.build_neural_network()
        self.feature_extractor = FeatureExtractor()

    def build_neural_network(self):
        model = tf.keras.Sequential([
            tf.keras.layers.Dense(128, activation='relu', input_shape=(20,)),
            tf.keras.layers.Dropout(0.3),
            tf.keras.layers.Dense(64, activation='relu'),
            tf.keras.layers.Dropout(0.2),
            tf.keras.layers.Dense(32, activation='relu'),
            tf.keras.layers.Dense(1, activation='sigmoid')
        ])

        model.compile(
            optimizer='adam',
            loss='binary_crossentropy',
            metrics=['accuracy']
        )

        return model

    def prepare_features(self, interactions):
        features = []

        for interaction in interactions:
            feature_vector = self.feature_extractor.extract({
                'time_since_interaction': self.calculate_time_since(interaction),
                'interaction_type': interaction['type'],
                'channel': interaction['channel'],
                'creative_performance': self.get_creative_performance(interaction),
                'user_segment': self.get_user_segment(interaction['user_id']),
                'time_of_day': self.get_time_features(interaction['timestamp']),
                'device_type': interaction['device_type'],
                'interaction_sequence': self.get_sequence_position(interaction)
            })

            features.append(feature_vector)

        return np.array(features)

    def predict_attribution_weights(self, interactions, conversion_data):
        features = self.prepare_features(interactions)
        probabilities = self.model.predict(features)

        # Normalize probabilities to sum to 1
        weights = probabilities / probabilities.sum()

        attribution_result = []
        for i, interaction in enumerate(interactions):
            attribution_result.append({
                'interaction': interaction,
                'weight': weights[i][0],
                'attributed_value': conversion_data['value'] * weights[i][0],
                'confidence': self.calculate_prediction_confidence(probabilities[i])
            })

        return attribution_result

Real-Time Processing

Stream processing:

# Real-time post-view post-click attribution
import kafka
import redis
from datetime import datetime, timedelta

class RealTimeAttributionProcessor:
    def __init__(self):
        self.kafka_consumer = kafka.KafkaConsumer(
            'user_interactions',
            'conversions',
            bootstrap_servers=['localhost:9092']
        )
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
        self.attribution_engine = PostViewPostClickAttribution()

    def process_interaction_stream(self):
        for message in self.kafka_consumer:
            if message.topic == 'user_interactions':
                self.process_interaction_event(message.value)
            elif message.topic == 'conversions':
                self.process_conversion_event(message.value)

    def process_interaction_event(self, interaction_data):
        user_id = interaction_data['user_id']

        # Store interaction in Redis with TTL
        interaction_key = f"interactions:{user_id}"
        ttl_seconds = 30 * 24 * 60 * 60  # 30 days

        self.redis_client.zadd(
            interaction_key,
            {json.dumps(interaction_data): interaction_data['timestamp']}
        )
        self.redis_client.expire(interaction_key, ttl_seconds)

    def process_conversion_event(self, conversion_data):
        user_id = conversion_data['user_id']

        # Retrieve relevant interactions from Redis
        interactions = self.get_user_interactions(user_id)

        # Calculate attribution in real-time
        attribution = self.attribution_engine.calculate_attribution(
            interactions,
            conversion_data
        )

        # Send attribution result to downstream systems
        self.send_attribution_result(attribution)

    def get_user_interactions(self, user_id):
        interaction_key = f"interactions:{user_id}"

        # Get interactions within attribution window
        cutoff_time = (datetime.now() - timedelta(days=30)).timestamp()

        interactions = self.redis_client.zrangebyscore(
            interaction_key,
            cutoff_time,
            '+inf',
            withscores=True
        )

        return [json.loads(data.decode()) for data, score in interactions]

BI Integration

Reporting dashboard:

-- Comprehensive post-view post-click attribution analysis
WITH attribution_analysis AS (
    SELECT 
        campaign_id,
        channel,
        interaction_type,
        COUNT(DISTINCT user_id) as unique_users,
        SUM(attributed_value) as total_attributed_value,
        AVG(attributed_value) as avg_attributed_value,
        SUM(CASE WHEN interaction_type = 'view' THEN attributed_value ELSE 0 END) as view_value,
        SUM(CASE WHEN interaction_type = 'click' THEN attributed_value ELSE 0 END) as click_value
    FROM post_view_post_click_attribution
    WHERE attribution_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
    GROUP BY campaign_id, channel, interaction_type
),
campaign_performance AS (
    SELECT 
        c.campaign_id,
        c.campaign_name,
        c.spend,
        aa.total_attributed_value,
        (aa.total_attributed_value - c.spend) / c.spend as roi,
        aa.view_value / aa.total_attributed_value as view_contribution,
        aa.click_value / aa.total_attributed_value as click_contribution
    FROM campaigns c
    JOIN attribution_analysis aa ON c.campaign_id = aa.campaign_id
)
SELECT 
    campaign_name,
    spend,
    total_attributed_value,
    roi,
    view_contribution,
    click_contribution,
    CASE 
        WHEN view_contribution > 0.3 THEN 'Brand-focused'
        WHEN click_contribution > 0.8 THEN 'Performance-focused'
        ELSE 'Balanced'
    END as campaign_type
FROM campaign_performance
ORDER BY roi DESC;

Post-view post-click attribution provides the most complete view of journey complexity and marketing effectiveness. The technical infrastructure and data management overhead are real, but the insights enable accurate ROI, better budget allocation, and clearer understanding of how channels work together.

Recommended for organizations with diverse channel portfolios, complex journeys, and significant investment in both brand awareness and performance marketing. The model excels when customers engage with multiple touchpoints across extended time before deciding.

About AI participation in writing articles

This article, like many others on our site, was created, written and proofread by a team of developers. Of course, not without the participation of AI assistants. We don't hide this and believe that modern systems are already quite good at handling simple tasks and, relatively speaking, writing an article about Viewport yourself is quite strange. It won't come out significantly better and will take a lot of time. But providing basic understanding to beginner webmasters is necessary. Of course, after the article is written by assistants - there's always proofreading, and this is where not one or two people participate, and only after that the article is published.

Ready to understand your complete customer journey?

Sign up for a free trial. Access advanced post-view post-click attribution, cross-device journey analysis, and comprehensive marketing impact tools.


Ready to take control of your web analytics? Try Statable free for 30 days — no credit card required, full feature access, GDPR-compliant by default. Start your free trial or view a live demo.