Virtual Stage Event Ended

This document describes the scope and payload for subscribers of the virtual-stage-event-ended event through webhooks.

Overview

The Virtual Stage Event Ended webhook sends comprehensive engagement data for all attendees after a live event concludes. Unlike the Custom Integration which sends individual user records, this webhook aggregates all users and sends them in configurable batches.

Key Differences:

  • Custom Integration: Sends one POST per user (e.g., 100 attendees = 100 requests)
  • Virtual Stage Event Ended: Sends all users in configurable batches (e.g., 100 attendees = 1-10 requests depending on batch size, or just 1 request if configured to send all)

Batch Size Options:

  • Configure batch size in webhook settings (10, 50, 100, 500, etc.)
  • Or select "Send All" to receive all users in a single request
  • Default is typically 50-100 users per batch

Scope

The event is subscribed at the company level.

  • Subscription Coverage: All events owned by the company and its child companies
  • Trigger: Event is invoked when the host clicks on the "End Event" or "End Event and Start Networking" button on the virtual stage
  • Frequency: Once per event (when ended)
  • Batch Configuration: Configure how many users to send per request, or send all users at once

Payload Structure

Request

A HTTP POST request is made to the endpoint registered as the webhook for the virtual stage event ended event.

Body

{
  "eventType": "virtual-stage-event-ended",
  "webHookInvocationId": "unique-web-hook-invocation-id",
  "correlationId": "unique-correlation-id-for-this-event",
  "timestamp": "2025-01-07T10:05:24.000Z",
  "details": {
    "companyId": "d5907d53-e996-4db5-9575-6b9c17b0df1e",
    "companyName": "Acme Corporation",
    "eventId": "8d6e1e40-9deb-430d-8c17-33bd26fe70a1",
    "eventName": "Q1 2025 Product Launch Webinar",
    "totalCount": 150,
    "page": 1,
    "size": 50,
    "users": [
      {
        "_id": "65e6bfaddab97d4fc595cc08",
        "uid": "380c2455-1a9c-49a5-b691-761563c495b3",
        "userId": "user123|||8d6e1e40-9deb-430d-8c17-33bd26fe70a1",
        "name": "John Doe",
        "email": "[email protected]",
        "eventId": "8d6e1e40-9deb-430d-8c17-33bd26fe70a1",
        "companyId": "d5907d53-e996-4db5-9575-6b9c17b0df1e",
        "attended": true,
        "viewedReplay": false,
        "registrationDate": "2025-01-05T06:27:51.414Z",
        "createdOn": "2025-01-07T06:46:03.025Z",
        "updatedOn": "2025-01-07T10:15:30.125Z",
        "liveTime": 45,
        "onDemandTime": 0,
        "averageLiveViewTime": 90,
        "averageOnDemandViewTime": 0,
        "liveTimeSpentPercentage": 90,
        "onDemandTimeSpentPercentage": 0,
        "engagementScore": 85,
        "leadScore": 42,
        "rawLeadScore": 168,
        "leadStatus": ["Attended"],
        "leadScoreDetails": {
          "attendedLiveSession": 10,
          "askedQuestion": 5,
          "answeredPoll": 3,
          "clickedCTA": 8,
          "viewedResource": 4
        },
        "questions": [
          {
            "id": "q1",
            "question": "Does this integrate with Salesforce?",
            "timestamp": "2025-01-07T10:08:15.000Z"
          },
          {
            "id": "q2",
            "question": "What's the pricing model?",
            "timestamp": "2025-01-07T10:12:30.000Z"
          }
        ],
        "questionsNumber": 2,
        "polls": [
          {
            "id": "poll1",
            "question": "What feature interests you most?",
            "answer": "API Integration",
            "timestamp": "2025-01-07T10:05:00.000Z"
          },
          {
            "id": "poll2",
            "question": "Would you recommend this?",
            "answer": "Yes",
            "timestamp": "2025-01-07T10:20:00.000Z"
          }
        ],
        "pollsNumber": 2,
        "ctaClicks": [
          {
            "id": "cta1",
            "name": "Download Whitepaper",
            "url": "https://example.com/whitepaper.pdf",
            "timestamp": "2025-01-07T10:10:00.000Z"
          },
          {
            "id": "cta2",
            "name": "Schedule Demo",
            "url": "https://example.com/demo",
            "timestamp": "2025-01-07T10:25:00.000Z"
          }
        ],
        "ctaClicksNumber": 2,
        "resourceClicks": [
          {
            "id": "res1",
            "name": "Product Deck",
            "url": "https://cdn.example.com/product-deck.pdf",
            "timestamp": "2025-01-07T10:15:00.000Z"
          }
        ],
        "resourceClicksNumber": 1,
        "circles": [],
        "circlesNumber": 0,
        "comments": 0,
        "messagesReactionsCount": 0
      },
      {
        "_id": "65e6c1b2dab97d4fc595cc09",
        "uid": "490d3566-2b0d-5aa6-c7a2-872674d5a6c4",
        "userId": "user456|||8d6e1e40-9deb-430d-8c17-33bd26fe70a1",
        "name": "Jane Smith",
        "email": "[email protected]",
        "eventId": "8d6e1e40-9deb-430d-8c17-33bd26fe70a1",
        "companyId": "d5907d53-e996-4db5-9575-6b9c17b0df1e",
        "attended": false,
        "viewedReplay": true,
        "registrationDate": "2025-01-04T14:22:15.414Z",
        "createdOn": "2025-01-08T09:30:00.000Z",
        "updatedOn": "2025-01-08T10:00:00.000Z",
        "liveTime": 0,
        "onDemandTime": 30,
        "averageLiveViewTime": 0,
        "averageOnDemandViewTime": 60,
        "liveTimeSpentPercentage": 0,
        "onDemandTimeSpentPercentage": 60,
        "engagementScore": 45,
        "leadScore": 18,
        "rawLeadScore": 72,
        "leadStatus": ["Viewed On Demand"],
        "leadScoreDetails": {
          "viewedRecording": 8,
          "clickedCTA": 5
        },
        "questions": [],
        "questionsNumber": 0,
        "polls": [],
        "pollsNumber": 0,
        "ctaClicks": [
          {
            "id": "cta1",
            "name": "Download Whitepaper",
            "url": "https://example.com/whitepaper.pdf",
            "timestamp": "2025-01-08T09:45:00.000Z"
          }
        ],
        "ctaClicksNumber": 1,
        "resourceClicks": [],
        "resourceClicksNumber": 0,
        "circles": [],
        "circlesNumber": 0,
        "comments": 0,
        "messagesReactionsCount": 0
      }
    ]
  }
}

User Engagement Fields

Each user object in the users array contains the following fields:

Identity Fields

FieldTypeDescription
_idStringMongoDB document ID
uidStringUnique user identifier
userIdStringComposite user ID (email|||eventId)
nameStringFull name of the user
emailStringEmail address of the user
eventIdStringID of the event
companyIdStringID of the company owning the event

Attendance & Timing

FieldTypeDescription
attendedBooleantrue if user attended the live event, false otherwise
viewedReplayBooleantrue if user watched the on-demand recording
registrationDateString (ISO 8601)When the user registered
createdOnString (ISO 8601)When the engagement record was created
updatedOnString (ISO 8601)When the engagement record was last updated

Viewing Time Metrics

FieldTypeDescription
liveTimeNumberMinutes watched during live event
onDemandTimeNumberMinutes watched on-demand/replay
averageLiveViewTimeNumberAverage percentage of live content watched
averageOnDemandViewTimeNumberAverage percentage of on-demand content watched
liveTimeSpentPercentageNumberPercentage of live event duration watched (0-100)
onDemandTimeSpentPercentageNumberPercentage of recording watched (0-100)

Engagement & Scoring

FieldTypeDescription
engagementScoreNumberOverall engagement score (0-100)
leadScoreNumberCalculated lead score
rawLeadScoreNumberRaw lead score before normalization
leadStatusArray[String]Lead status values (e.g., ["Attended"], ["Viewed On Demand"])
leadScoreDetailsObjectBreakdown of lead score components

Lead Score Details Object

{
  "attendedLiveSession": 10,
  "askedQuestion": 5,
  "answeredPoll": 3,
  "clickedCTA": 8,
  "viewedResource": 4,
  "viewedRecording": 8,
  "timeSpent": 12
}

Questions

FieldTypeDescription
questionsArrayArray of question objects asked by the user
questionsNumberNumberTotal number of questions asked

Question Object:

{
  "id": "q1",
  "question": "Question text",
  "timestamp": "2025-01-07T10:08:15.000Z"
}

Polls

FieldTypeDescription
pollsArrayArray of poll response objects
pollsNumberNumberTotal number of polls answered

Poll Object:

{
  "id": "poll1",
  "question": "Poll question text",
  "answer": "User's answer",
  "timestamp": "2025-01-07T10:05:00.000Z"
}

CTA Clicks

FieldTypeDescription
ctaClicksArrayArray of CTA click objects
ctaClicksNumberNumberTotal number of CTAs clicked

CTA Click Object:

{
  "id": "cta1",
  "name": "CTA name/title",
  "url": "https://example.com/destination",
  "timestamp": "2025-01-07T10:10:00.000Z"
}

Resource Clicks

FieldTypeDescription
resourceClicksArrayArray of resource click objects
resourceClicksNumberNumberTotal number of resources clicked

Resource Click Object:

{
  "id": "res1",
  "name": "Resource name",
  "url": "https://cdn.example.com/resource.pdf",
  "timestamp": "2025-01-07T10:15:00.000Z"
}

Social Engagement

FieldTypeDescription
circlesArrayArray of circles (networking groups) the user joined
circlesNumberNumberTotal number of circles joined
commentsNumberNumber of comments posted
messagesReactionsCountNumberNumber of reactions to messages

Headers

The following headers are included in the request:

Header KeyValueDescription
x-introvoke-web-hook-authAuthentication keyThe key to validate the authenticity of the request
user-agentintrovoke-web-hookStatic value
content-typeapplication/jsonStatic value

Validating Requests

Always validate the x-introvoke-web-hook-auth header to ensure the request is legitimate:

const expectedAuthKey = process.env.WEBHOOK_AUTH_KEY;
const receivedAuthKey = req.headers['x-introvoke-web-hook-auth'];

if (receivedAuthKey !== expectedAuthKey) {
  return res.status(401).json({ error: 'Unauthorized' });
}

Responses

The expected responses from the webhook endpoint:

HTTP Response CodeDescriptionRetry?
200The message was accepted and request was successfulN/A
400The message was malformed❌ Do not retry
401The authentication failed and message could not be processed❌ Do not retry
404The endpoint does not exist❌ Do not retry
429Request quota was exceeded✅ Retry after some time
500Internal server error✅ Retry after some time

Best Practices for Responses

  1. Respond Quickly: Return a 200 response within 5 seconds
  2. Process Asynchronously: Queue the data for processing after responding
  3. Handle Errors Gracefully: Log errors but still return appropriate status codes
  4. Implement Idempotency: Use webHookInvocationId to detect and handle duplicates

Example:

app.post('/webhook/event-ended', async (req, res) => {
  // Validate auth
  if (req.headers['x-introvoke-web-hook-auth'] !== expectedKey) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Respond immediately
  res.status(200).json({ success: true });

  // Process asynchronously
  const payload = req.body;
  processEventData(payload).catch(err => {
    console.error('Error processing webhook:', err);
  });
});

Pagination


When an event has many attendees, the data is paginated to keep payloads manageable.


Pagination Fields

FieldTypeDescription
totalCountNumberTotal number of users across all pages
pageNumberCurrent page number (1-indexed)
sizeNumberNumber of users in this page

Configuring Batch Size


Batch size is configurable! When setting up your webhook subscription, you can specify:

  • Custom Batch Size: Set the number of users per request (e.g., 10, 50, 100, 500)
    • Send All at Once: Configure to send all users in a single request (no pagination)

      Configuration Options:

      SettingExampleUse Case
      Small batches10-25 usersReal-time processing, rate-limited APIs
      Medium batches50-100 usersBalanced approach (default)
      Large batches250-500 usersBulk imports, data warehouses
      No paginationAll usersSmall events (<100), systems optimized for large payloads

      To Configure:

      1. Go to your webhook subscription settings in the Sequel Dashboard
      2. Find the "Batch Size" or "Users Per Request" setting
      3. Enter your preferred size or select "Send All"

Handling Pagination

You'll receive multiple webhook invocations for the same event if there are more users than fit in one configured page.


Identify pages for the same event:

  • Same eventId in details
  • Same correlationId (groups all pages together)
  • Different webHookInvocationId for each page
  • Different page number