Skip to main content

Booking Flow Routers

The BookingFlowRouter entity provides a dynamic, rule-based routing system that directs users to the appropriate booking flow based on various matching criteria. This system enables organizations to implement sophisticated traffic management strategies across their booking experiences without requiring code changes. Each router can be configured with specific matching criteria (URL paths, query parameters, campaign tags) and has a well-defined priority to resolve conflicts.

Schema Definition

type BookingFlowRouter = BaseEntityWithOrganization & {
// Base Properties
_id: string; // MongoDB ObjectId
organization_id: string; // Required organization reference
name: string; // Required name (1-100 chars)
description?: string; // Optional description
booking_flow_id: string; // Required booking flow reference
example_urls: string[]; // Example URLs for documentation/testing

// Matching Configuration
matching_criteria: MatchingCriteria; // Rules for URL matching
priority_class: PriorityClass; // Router priority classification
is_default: boolean; // Whether this is a fallback router
is_active: boolean; // Whether the router is active

// Time-based Activation
scheduling?: {
active_from?: Date; // Optional start time
active_until?: Date; // Optional end time
};

// Timestamps
createdAt: Date; // Auto-generated
updatedAt: Date; // Auto-updated
};

Matching Criteria Structure

type MatchingCriteria = {
path?: PathCriteria; // URL path segment matching
query_params?: QueryParamCriteria; // Query parameter matching
campaign?: CampaignCriteria; // UTM campaign parameter matching
};

type PathCriteria = {
segments: string[]; // Path segments to match
};

type QueryParamCriteria = {
parameters: Record<string, QueryParamConfig>; // Query parameters to match
};

type QueryParamConfig = {
value?: string; // Optional specific value to match
required?: boolean; // Whether parameter is required (defaults to true)
};

type CampaignCriteria = {
utm_source?: string; // UTM source parameter
utm_medium?: string; // UTM medium parameter
utm_campaign?: string; // UTM campaign parameter
utm_content?: string; // UTM content parameter
utm_term?: string; // UTM term parameter
};

Field Descriptions

Base Properties

FieldTypeRequiredUniqueDescription
_idObjectIdYesYesUnique identifier for the router
organization_idObjectIdYesNoOrganization that owns the router
namestringYesYes*User-friendly name (1-100 characters)
descriptionstringNoNoOptional description of router purpose
booking_flow_idObjectIdYesNoReference to the booking flow to route to
example_urlsstring[]NoNoExample URLs that should match this router
matching_criteriaMatchingCriteriaYes**NoConfiguration for URL matching
priority_classPriorityClassYesNoRouter's priority class for match ordering
is_defaultbooleanYesNo***Whether this is a fallback/default router
is_activebooleanYesNoWhether the router is currently active
schedulingSchedulingConfigNoNoOptional time-based activation window

*Name must be unique within an organization context
**Not required for default routers
***Only one default router can be active per organization

Enumerations

enum PriorityClass {
CAMPAIGN = 'campaign', // Highest priority - matches UTM parameters
PATH_AND_QUERY = 'path_and_query', // Matches both path segments and query parameters
PATH_ONLY = 'path_only', // Matches only path segments
QUERY_ONLY = 'query_only', // Matches only query parameters
DEFAULT = 'default' // Lowest priority - default fallback
}

API DTOs

Create DTO

type CreateBookingFlowRouterDto = {
name: string; // Required, min length 1, max length 100
description?: string; // Optional description
booking_flow_id: string; // Required booking flow reference
example_urls?: string[]; // Optional example URLs
matching_criteria: MatchingCriteria; // Required matching configuration
is_default?: boolean; // Optional, defaults to false
is_active?: boolean; // Optional, defaults to true
scheduling?: {
active_from?: Date; // Optional activation start time
active_until?: Date; // Optional activation end time
};
};

Update DTO

type UpdateBookingFlowRouterDto = {
name?: string; // Optional name update
description?: string; // Optional description update
booking_flow_id?: string; // Optional booking flow reference update
example_urls?: string[]; // Optional example URLs update
matching_criteria?: MatchingCriteria; // Optional matching configuration update
is_default?: boolean; // Optional default status update
is_active?: boolean; // Optional active status update
scheduling?: {
active_from?: Date | null; // Optional activation start time update (null to remove)
active_until?: Date | null; // Optional activation end time update (null to remove)
};
};

Filter DTO

type FilterBookingFlowRouterDto = {
name?: string | { $regex: string }; // Optional name filter (exact or regex)
booking_flow_id?: string; // Optional booking flow filter
is_default?: boolean; // Optional default status filter
is_active?: boolean; // Optional active status filter
populate?: boolean; // Whether to populate booking flow reference
};

Validation DTOs

type ValidateExampleUrlsDto = {
example_urls: string[]; // URLs to validate against existing routers
};

type ValidateRouterRulesDto = {
matching_criteria: MatchingCriteria; // Matching criteria to validate
example_urls: string[]; // Example URLs to check against the criteria
exclude_router_id?: string; // Optional ID to exclude (for updating routers)
};

Resolution DTO

type ResolveBookingFlowRouterRequestDto = {
url: string; // URL to resolve a matching router for
};

type ResolveBookingFlowRouterResponseDto = {
router: BookingFlowRouter; // The matched router
booking_flow: BookingFlowPopulated; // The associated booking flow (populated)
brand_kit: BrandKitPopulated; // The organization's brand kit
matched_by: string; // Description of how the match occurred
priority_class: PriorityClass; // The priority class that matched
};

Relationships

Primary Relationships

  • BookingFlowRouter → Organization (Many-to-One, Required)

    • Each router must belong to a specific organization
    • Referenced via organization_id field
    • Enforces multi-tenant isolation
    • Repository methods validate organization ownership
  • BookingFlowRouter → BookingFlow (Many-to-One, Required)

    • Each router points to a specific booking flow
    • Referenced via booking_flow_id field
    • Booking flow must exist and belong to the same organization
    • Can be populated in API responses

Resolution and Matching Logic

Priority Class Hierarchy

Routers are evaluated in strict priority order:

  1. CAMPAIGN (Highest) - Matches UTM parameters in query string
  2. PATH_AND_QUERY - Matches both path segments and query parameters
  3. PATH_ONLY - Matches only URL path segments
  4. QUERY_ONLY - Matches only query parameters
  5. DEFAULT (Lowest) - Fallback routers with no matching criteria

Specificity-Based Ordering

Within each priority class, routers are ranked by specificity:

Priority ClassSpecificity Calculation
CAMPAIGNNumber of UTM parameters specified
PATH_AND_QUERY(Number of path segments × 1000) + Number of query parameters
PATH_ONLYNumber of path segments
QUERY_ONLYNumber of query parameters
DEFAULTNot applicable (no specificity)

If specificity is tied, the most recently created router wins.

Path Segment Matching Rules

  1. Exact Path Length - The number of segments in the URL path must match exactly
  2. Exact Segment Order - Segments are matched in the exact specified order
  3. Case-Sensitive - Path segment matching is case-sensitive
  4. No Partial Matching - The entire path must match; no partial matches

Query Parameter Matching Rules

  1. Parameter Existence - Query parameters can be matched by existence only (no specific value)
  2. Parameter Value - Query parameters can be matched by specific value
  3. Required Parameters - Parameters can be marked as required or optional
  4. Case-Sensitive - Query parameter matching is case-sensitive

Campaign Parameter Matching

Campaign matching focuses on UTM parameters:

  • utm_source
  • utm_medium
  • utm_campaign
  • utm_content
  • utm_term

All specified campaign parameters must match exactly for the router to match.

Time-Based Scheduling

Routers can have optional time-based activation:

  1. Activation Window - Routers are only considered if the current time is:
    • After active_from (if specified)
    • Before active_until (if specified)
  2. Default Routers - Default routers ignore scheduling constraints
  3. No Scheduling - If both active_from and active_until are null, the router is always considered

Validation and Conflict Detection

URL Validation

The module provides specialized validation endpoints:

  1. Example URL Validation - Verify if example URLs would match existing routers
  2. Router Rules Validation - Check if proposed matching criteria would conflict with existing routers

Conflict Detection

Conflict detection evaluates:

  1. Matching Conflicts - If a URL would match both the proposed router and existing routers
  2. Example URL Conflicts - If a URL is listed as an example URL in any existing router
  3. Specificity Analysis - Which router would win in case of multiple matches

Create/Update Validation

All create and update operations are validated for:

  1. Existence and Ownership - Referenced booking flow must exist and belong to the same organization
  2. Default Router Uniqueness - Only one default router can be active per organization
  3. Matching Criteria Validity - Criteria must follow the expected structure
  4. Name Uniqueness - Router name must be unique within an organization
  5. Example URL Format - Example URLs must be valid and properly formatted

Rules Engine Architecture

The module uses dedicated rules engines to encapsulate complex routing logic:

BookingFlowRouterRulesEngine

The primary rules engine handles URL-to-router matching:

  1. Router Filtering - Filters routers based on active status and scheduling
  2. Priority Class Grouping - Groups routers by priority class
  3. Matcher Functions - Provides specialized matchers for different criteria types
  4. Specificity Calculation - Computes specificity scores for ranking
  5. Router Selection - Returns the most appropriate router and match details

BookingFlowRouterConflictsRulesEngine

A specialized engine that analyzes router configurations for conflicts:

  1. Conflict Detection - Identifies overlapping matching criteria
  2. Warning Generation - Produces warnings about potential conflicts
  3. Conflict Severity - Ranks conflicts by severity level
  4. Resolution Suggestions - Provides guidance on resolving conflicts

Implementation Examples

Creating a Router

// Example API request to create a path-based router
const createDto = {
name: "Services Router",
description: "Routes URLs with /services path to the standard booking flow",
booking_flow_id: "60f7b0b9e6b3f3b4e8b4b0b9",
matching_criteria: {
path: {
segments: ["services"]
}
},
example_urls: [
"https://example.com/services",
"https://example.com/services?ref=email"
],
is_active: true
};

// Internal flow in service layer
const newRouter = await bookingFlowRouterRepository.create({
organization_id: new Types.ObjectId(organizationId),
name: createDto.name,
description: createDto.description,
booking_flow_id: new Types.ObjectId(createDto.booking_flow_id),
matching_criteria: createDto.matching_criteria,
priority_class: determinePriorityClass(createDto),
example_urls: createDto.example_urls,
is_default: createDto.is_default || false,
is_active: createDto.is_active ?? true
});

Resolving a Router for a URL

// Example API request to resolve a router
const resolveDto = {
url: "https://example.com/services/hvac?utm_source=google&utm_medium=cpc"
};

// Internal flow in service layer
const resolveRouter = async (organizationId: string, url: string) => {
// 1. Process URL to extract path and query parameters
const parsedUrl = processUrl(url);
const request = createRouterMatchRequest(parsedUrl);

// 2. Get active routers for the organization
const routers = await bookingFlowRouterRepository.findActiveByOrganizationId(organizationId);

// 3. Use the rules engine to evaluate routers against the URL
const result = rulesEngine.evaluateRouters(routers, request);

// 4. Log router resolution event for analytics
if (result.selectedRouter) {
await routerEventService.createRouterEvent({
organization_id: organizationId,
router_id: result.selectedRouter._id,
url: url,
event_type: RouterEventType.ROUTER_RESOLVED,
metadata: {
matched_class: result.matchedClass,
timestamp: new Date()
}
});
}

// 5. Populate related entities and return result
return {
router: result.selectedRouter,
booking_flow: await bookingFlowService.findById(
organizationId,
result.selectedRouter.booking_flow_id.toString()
),
brand_kit: await brandKitService.findByOrganizationId(organizationId),
matched_by: result.reason,
priority_class: result.matchedClass
};
};

Validating Router Rules

// Example API request to validate router rules
const validateDto = {
matching_criteria: {
path: {
segments: ["services", "hvac"]
}
},
example_urls: [
"https://example.com/services/hvac",
"https://example.com/services/hvac?promo=spring2025"
]
};

// Internal flow in service layer
const validateRouterRules = async (organizationId: string, validateDto) => {
// 1. Get active routers for organization
const routers = await bookingFlowRouterRepository.findActiveByOrganizationId(organizationId);

// 2. Filter out excluded router if updating
const filteredRouters = validateDto.exclude_router_id
? routers.filter(r => r._id.toString() !== validateDto.exclude_router_id)
: routers;

// 3. Create temporary router with proposed criteria
const temporaryRouter = {
matching_criteria: validateDto.matching_criteria,
_id: 'temp-router',
name: 'Temporary Router',
booking_flow_id: 'temp-flow',
organization_id: organizationId,
is_active: true,
is_default: false,
priority_class: determinePriorityClass(validateDto),
createdAt: new Date(),
updatedAt: new Date()
};

// 4. Validate each example URL
const results = await Promise.all(validateDto.example_urls.map(async (url) => {
const parsedUrl = processUrl(url);
const request = createRouterMatchRequest(parsedUrl);

// Check if existing routers would match
const existingMatches = rulesEngine.evaluateRouters(filteredRouters, request);

// Check if proposed router would match
const proposedMatches = rulesEngine.evaluateRouters([temporaryRouter], request);

return {
url,
isValidUrl: true,
proposedRouterMatches: proposedMatches.matchedRouters.length > 0,
hasConflicts: proposedMatches.matchedRouters.length > 0 && existingMatches.matchedRouters.length > 0,
existingMatches: existingMatches.matchedRouters.map(m => ({
router_id: m.router._id,
router_name: m.router.name,
priority_class: m.router.priority_class,
booking_flow_id: m.router.booking_flow_id
}))
};
}));

// 5. Generate recommendation based on results
return {
results,
recommendation: {
hasConflicts: results.some(r => r.hasConflicts),
message: generateRecommendationMessage(results)
}
};
};

Security Considerations

Multi-Tenant Isolation

  1. Organization Scoping

    • All routers belong to a specific organization
    • Service methods validate organization ownership
    • Repository methods filter by organization ID
    • API endpoints extract organization from session
  2. Router-Level Security

    • Ownership validation for all router operations
    • Conflict detection to prevent unintended routing
    • Default router protection to ensure fallback functionality
    • Permission checking for all controller operations

Validation and Sanitization

  1. URL Validation

    • All example URLs are validated for proper format
    • URL processing sanitizes inputs before matching
    • Domain validation can be configured via settings
  2. Conflict Protection

    • Validation endpoints to check for conflicts before create/update
    • Explicit conflict messaging to guide users
    • Conflict severity indication
  3. Permission-Based Access Control

    • Required permissions for router creation and management
    • Read permissions for router resolution
    • Organization-level permission checks
    • Audit logging through router events