SecurityData Flow Diagram

Data Flow Diagram

This document provides a comprehensive overview of how data flows through the Ledly platform. It is intended for security reviewers, compliance teams, and technical architects evaluating Ledly for their higher education institution.

Ledly processes student lead data for higher education enrollment management. All data flows are encrypted in transit using TLS 1.3 and at rest using AES-256.


Overview

Ledly is a lead management platform designed for higher education. Data enters the system through multiple ingestion channels, is processed and enriched, then synchronized to CRM systems and delivered via webhooks.

High-Level Architecture

                                    LEDLY PLATFORM
                    +------------------------------------------+
                    |                                          |
    INGESTION       |     +-------------+    +-----------+     |      DELIVERY
   +---------+      |     |             |    |           |     |     +---------+
   | Web     |----->|---->|   Ledly     |--->| PostgreSQL|     |---->| HubSpot |
   | Forms   |      |     |   API       |    | Database  |     |     +---------+
   +---------+      |     |             |    |           |     |
                    |     +------+------+    +-----+-----+     |     +---------+
   +---------+      |            |                 |           |---->|Dynamics |
   | Landing |----->|------------+                 |           |     |   365   |
   | Pages   |      |            |                 |           |     +---------+
   +---------+      |            v                 |           |
                    |     +-------------+          |           |     +---------+
   +---------+      |     |  Webhook    |<---------+           |---->| Custom  |
   | API     |----->|     |  Service    |                      |     | Webhook |
   | Clients |      |     +-------------+                      |     +---------+
   +---------+      |                                          |
                    +------------------------------------------+

Data Categories

CategoryDescriptionSensitivity
Lead PIINames, emails, phone numbers, addressesHigh
Education DataProgram interests, start dates, campus preferencesMedium
Marketing DataUTM parameters, referral sources, campaign IDsLow
System DataTimestamps, IDs, sync status, API logsLow

Lead Ingestion Flow

Leads enter Ledly through three primary channels. All ingestion endpoints require authentication and validate data before storage.

Ingestion Channels

+------------------+     HTTPS/TLS 1.3      +------------------+
|   WEB FORMS      |----------------------->|                  |
| (JavaScript SDK) |   POST /api/leads/inbound   |                  |
+------------------+        + API Key       |                  |
                                            |                  |
+------------------+     HTTPS/TLS 1.3      |    LEDLY API     |
|  LANDING PAGES   |----------------------->|                  |
|  (Form Submit)   |   POST /api/leads/inbound   |   - Validates    |
+------------------+        + API Key       |   - Deduplicates |
                                            |   - Enriches     |
+------------------+     HTTPS/TLS 1.3      |   - Routes       |
|   API CLIENTS    |----------------------->|                  |
| (Server-to-Server)|  POST /api/leads/inbound   |                  |
+------------------+        + API Key       +--------+---------+
                                                     |
                                                     | Validated Lead
                                                     v
                                            +------------------+
                                            |   PostgreSQL     |
                                            |   Database       |
                                            |                  |
                                            | - leads table    |
                                            | - Encrypted PII  |
                                            | - org_id scope   |
                                            +------------------+

Ingestion Process

  1. Authentication: API key validated against api_keys table
  2. Rate Limiting: Request checked against rate limits (100 req/10s default)
  3. Validation: Schema validation, email format, required fields
  4. Deduplication: Email-based duplicate check within organization
  5. Enrichment: Optional field enrichment and normalization
  6. Storage: Lead persisted to PostgreSQL with organization_id scope
  7. Event Emission: lead.created event triggers downstream processing

Data Transformation

Input FieldValidationTransformationStorage
emailRFC 5322 formatLowercase, trimleads.email
phoneE.164 formatNormalize to E.164leads.phone
first_name1-100 charsTrim whitespaceleads.first_name
last_name1-100 charsTrim whitespaceleads.last_name
utm_*1-500 charsNoneleads.utm_data (JSONB)
⚠️

All ingestion requests must include a valid API key in the Authorization header. Requests without authentication receive a 401 response.


CRM Synchronization Flow

Ledly synchronizes leads bidirectionally with CRM systems. Each CRM integration uses OAuth 2.0 for secure authorization.

HubSpot Integration

LEDLY                                              HUBSPOT
+------------------+                              +------------------+
|                  |    OAuth 2.0 Authorization   |                  |
|  CRM Settings    |<---------------------------->|  Developer App   |
|                  |    (Initial Setup)           |                  |
+--------+---------+                              +------------------+
         |
         | Stores: access_token, refresh_token
         v
+------------------+                              +------------------+
|                  |     POST /crm/v3/objects/    |                  |
|  Lead Created    |----------------------------->|    Contacts      |
|  or Updated      |     contacts (HTTPS)         |                  |
|                  |     + Bearer Token           |                  |
+--------+---------+                              +--------+---------+
         |                                                 |
         |                                                 |
         v                                                 v
+------------------+                              +------------------+
|                  |     POST /api/hubspot/       |                  |
|  Update Lead     |<-----------------------------|    Webhook       |
|  Status          |     webhook (HTTPS)          |    Event         |
|                  |     + Signature              |                  |
+------------------+                              +------------------+

OAuth Scopes Required:

  • crm.objects.contacts.read
  • crm.objects.contacts.write
  • crm.schemas.contacts.read

Token Storage:

DataStorageEncryption
Access Tokencrm_connections.access_tokenAES-256
Refresh Tokencrm_connections.refresh_tokenAES-256
Client Secretcrm_connections.client_secretAES-256

Microsoft Dynamics 365 Integration

LEDLY                                              DYNAMICS 365
+------------------+                              +------------------+
|                  |    Azure AD OAuth 2.0        |                  |
|  CRM Settings    |<---------------------------->|  Azure AD App    |
|                  |    (Initial Setup)           |  Registration    |
+--------+---------+                              +------------------+
         |
         | Stores: access_token, refresh_token, tenant_id
         v
+------------------+                              +------------------+
|                  |     POST /api/data/v9.2/     |                  |
|  Lead Created    |----------------------------->|    leads         |
|  or Updated      |     leads (HTTPS/OData)      |    (Entity)      |
|                  |     + Bearer Token           |                  |
+--------+---------+                              +--------+---------+
         |                                                 |
         |                                                 |
         v                                                 v
+------------------+                              +------------------+
|                  |     POST /api/dynamics/      |  Power Automate  |
|  Update Lead     |<-----------------------------|    Flow          |
|  Status          |     webhook (HTTPS)          |                  |
|                  |     + Webhook Secret         |                  |
+------------------+                              +------------------+

Azure AD Permissions:

  • Dynamics CRM > user_impersonation (Delegated)

Authentication Flow:

  1. User initiates OAuth in Ledly settings
  2. Redirect to Azure AD authorization endpoint
  3. User consents to permissions
  4. Authorization code exchanged for tokens
  5. Tokens stored encrypted in database
  6. Access token auto-refreshed before expiration (1 hour)

CRM Sync Status

Each lead tracks its CRM synchronization state:

StatusDescription
pendingQueued for sync
syncedSuccessfully pushed to CRM
failedSync failed, will retry
not_connectedNo CRM integration configured

Webhook Delivery Flow

Ledly delivers real-time notifications to customer endpoints when lead events occur.

Webhook Architecture

+------------------+     Event Bus        +------------------+
|                  |                      |                  |
|  Lead Event      |--------------------->|  Webhook         |
|  (Created,       |   lead.created       |  Service         |
|   Updated, etc.) |   lead.updated       |                  |
|                  |   lead.status_changed|                  |
+------------------+                      +--------+---------+
                                                   |
                        For each subscribed endpoint:
                                                   |
                        +----------+---------------+----------------+
                        |          |               |                |
                        v          v               v                v
               +--------+--+ +-----+------+ +------+-----+ +--------+---+
               |           | |            | |            | |            |
               | Customer  | | Customer   | | Zapier     | | Slack      |
               | API #1    | | API #2     | | Webhook    | | Webhook    |
               |           | |            | |            | |            |
               +-----------+ +------------+ +------------+ +------------+
                  HTTPS         HTTPS          HTTPS          HTTPS
               + Signature   + Signature    + Signature    + Signature

Webhook Request Structure

Every webhook request includes security headers for verification:

HeaderDescription
X-Ledly-SignatureHMAC-SHA256 signature of payload
X-Ledly-TimestampUnix timestamp of request
X-Ledly-Webhook-IdUnique webhook configuration ID
X-Ledly-Delivery-IdUnique delivery attempt ID

Signature Verification

Signature Generation:
+------------------+
|                  |
|  Payload (JSON)  |
|                  |
+--------+---------+
         |
         v
+------------------+     +------------------+
|                  |     |                  |
|  timestamp.      |---->|  HMAC-SHA256     |
|  payload         |     |  (webhook_secret)|
|                  |     |                  |
+------------------+     +--------+---------+
                                  |
                                  v
                         +------------------+
                         |                  |
                         |  X-Ledly-        |
                         |  Signature       |
                         |                  |
                         +------------------+

Verification Steps (Recipient):

  1. Extract X-Ledly-Timestamp header
  2. Reject if timestamp > 5 minutes old (replay protection)
  3. Concatenate: {timestamp}.{raw_body}
  4. Compute HMAC-SHA256 with webhook secret
  5. Compare signatures using constant-time comparison

Retry Logic

Failed webhook deliveries are automatically retried with exponential backoff:

Attempt 1 (immediate)
    |
    | Failed (non-2xx or timeout)
    v
Attempt 2 (+1 minute)
    |
    | Failed
    v
Attempt 3 (+5 minutes)
    |
    | Failed
    v
Attempt 4 (+30 minutes)
    |
    | Failed
    v
Attempt 5 (+2 hours)
    |
    | Failed
    v
Attempt 6 (+24 hours)
    |
    | Failed
    v
MARKED AS FAILED (manual retry available)
AttemptDelayCumulative Time
1Immediate0
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hours2.5 hours
624 hours26.5 hours

Webhook deliveries timeout after 30 seconds. For long-running processes, acknowledge the webhook immediately and process asynchronously.


Data Storage

All persistent data is stored in PostgreSQL with strict organization-level isolation.

Database Architecture

+------------------------------------------------------------------+
|                        PostgreSQL Database                        |
|                        (AWS RDS / Encrypted)                      |
+------------------------------------------------------------------+
|                                                                   |
|  +-------------------+    +-------------------+                   |
|  | organizations     |    | users             |                   |
|  |-------------------|    |-------------------|                   |
|  | id (PK)           |<---| organization_id   |                   |
|  | name              |    | email             |                   |
|  | settings (JSONB)  |    | password_hash     |                   |
|  | created_at        |    | role              |                   |
|  +-------------------+    +-------------------+                   |
|           |                                                       |
|           | 1:N                                                   |
|           v                                                       |
|  +-------------------+    +-------------------+                   |
|  | leads             |    | api_keys          |                   |
|  |-------------------|    |-------------------|                   |
|  | id (PK)           |    | id (PK)           |                   |
|  | organization_id   |--->| organization_id   |                   |
|  | email             |    | key_hash          |                   |
|  | first_name        |    | permissions       |                   |
|  | last_name         |    | ip_restrictions   |                   |
|  | phone             |    | last_used_at      |                   |
|  | utm_data (JSONB)  |    +-------------------+                   |
|  | custom_fields     |                                            |
|  | crm_sync_status   |    +-------------------+                   |
|  | created_at        |    | crm_connections   |                   |
|  +-------------------+    |-------------------|                   |
|                           | id (PK)           |                   |
|                           | organization_id   |                   |
|  +-------------------+    | provider          |                   |
|  | webhook_configs   |    | access_token (E)  |                   |
|  |-------------------|    | refresh_token (E) |                   |
|  | id (PK)           |    | client_secret (E) |                   |
|  | organization_id   |    | expires_at        |                   |
|  | url               |    +-------------------+                   |
|  | secret_hash       |         (E) = Encrypted                    |
|  | events            |                                            |
|  | active            |    +-------------------+                   |
|  +-------------------+    | webhook_deliveries|                   |
|                           |-------------------|                   |
|                           | id (PK)           |                   |
|                           | webhook_id        |                   |
|                           | lead_id           |                   |
|                           | status            |                   |
|                           | attempts          |                   |
|                           | response_code     |                   |
|                           +-------------------+                   |
+------------------------------------------------------------------+

Data Stored by Table

TableData StoredRetention
organizationsAccount details, billing info, settingsAccount lifetime
usersUser profiles, hashed passwords, rolesAccount lifetime
leadsLead PII, UTM data, custom fields, statusConfigurable (default: indefinite)
api_keysHashed API keys, permissions, usage statsUntil revoked
crm_connectionsOAuth tokens (encrypted), provider configUntil disconnected
webhook_configsEndpoint URLs, event subscriptions, secretsUntil deleted
webhook_deliveriesDelivery attempts, responses, timestamps30 days
audit_logsUser actions, API calls, security events90 days

Data Isolation

All queries are scoped by organization_id to ensure strict tenant isolation:

-- Example: All lead queries include organization_id
SELECT * FROM leads
WHERE organization_id = :current_org_id
  AND email = :email;
 
-- Row-Level Security enforced at database level
ALTER TABLE leads ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY leads_org_isolation ON leads
  USING (organization_id = current_setting('app.organization_id')::uuid);
⚠️

Cross-organization data access is not possible through the API. Row-level security is enforced at the database level as an additional safeguard.


Authentication Flow

Ledly uses JWT tokens for user authentication and API keys for vendor/system authentication.

User Authentication (JWT)

+------------------+                              +------------------+
|                  |     POST /api/auth/login     |                  |
|  User            |----------------------------->|  Ledly API       |
|  (Browser)       |     email + password         |                  |
|                  |                              +--------+---------+
+--------+---------+                                       |
         ^                                                 |
         |                                                 v
         |                                        +------------------+
         |                                        |                  |
         |                                        |  Verify password |
         |                                        |  (bcrypt hash)   |
         |                                        |                  |
         |                                        +--------+---------+
         |                                                 |
         |     200 OK                                      |
         |     { token: "eyJ...", expires_at: "..." }      |
         |<------------------------------------------------+
         |
         |
         |     GET /api/leads
         |     Authorization: Bearer eyJ...
         +------------------------------------------------>|
                                                           |
                                                  +--------+---------+
                                                  |                  |
                                                  |  Verify JWT      |
                                                  |  - Signature     |
                                                  |  - Expiration    |
                                                  |  - Claims        |
                                                  |                  |
                                                  +--------+---------+
                                                           |
         |<------------------------------------------------+
         |     200 OK { leads: [...] }

JWT Token Structure:

ClaimDescription
subUser ID
orgOrganization ID
roleUser role (admin, member, viewer)
expExpiration timestamp (24 hours)
iatIssued at timestamp

Token Security:

  • Signed with HS256 using server-side secret
  • 24-hour expiration
  • Refresh via /api/auth/refresh endpoint
  • Invalidated on logout or password change

API Key Authentication

+------------------+                              +------------------+
|                  |     POST /api/leads/inbound       |                  |
|  Vendor Server   |----------------------------->|  Ledly API       |
|                  |     Authorization: Bearer vk_live_...   |                  |
|                  |                              +--------+---------+
+------------------+                                       |
                                                           v
                                                  +------------------+
                                                  |                  |
                                                  |  Hash API Key    |
                                                  |  Lookup in DB    |
                                                  |  Check:          |
                                                  |  - Active        |
                                                  |  - IP allowed    |
                                                  |  - Rate limit    |
                                                  |                  |
                                                  +--------+---------+
                                                           |
                                                  +--------+---------+
                                                  |                  |
                                                  |  Process Request |
                                                  |  (scoped to      |
                                                  |   organization)  |
                                                  |                  |
                                                  +------------------+

API Key Security:

  • Keys hashed with SHA-256 before storage
  • Optional IP allowlist restrictions
  • Per-key rate limiting
  • Separate test (vk_test_) and production (vk_live_) keys

Data Types Reference

Complete reference of data types flowing through Ledly:

Data TypeSourceStorage LocationDestinationsEncryption
Lead PIIForms, API, Importsleads tableCRM, WebhooksAt rest (AES-256)
EmailIngestionleads.emailCRM, WebhooksAt rest
PhoneIngestionleads.phoneCRM, WebhooksAt rest
NameIngestionleads.first_name, leads.last_nameCRM, WebhooksAt rest
AddressIngestionleads.address_* columnsCRM, WebhooksAt rest
UTM DataForms, Query Paramsleads.utm_data (JSONB)CRM, WebhooksAt rest
utm_sourceQuery stringJSONB fieldCRM custom fieldAt rest
utm_mediumQuery stringJSONB fieldCRM custom fieldAt rest
utm_campaignQuery stringJSONB fieldCRM custom fieldAt rest
Education DataForms, Custom Fieldsleads.custom_fieldsCRM, WebhooksAt rest
Program interestForm fieldJSONB fieldCRM custom fieldAt rest
Start dateForm fieldJSONB fieldCRM custom fieldAt rest
CampusForm fieldJSONB fieldCRM custom fieldAt rest
CRM CredentialsOAuth flowcrm_connectionsCRM API onlyAES-256 encrypted
Access tokensOAuthEncrypted columnBearer authAES-256
Refresh tokensOAuthEncrypted columnToken refreshAES-256
Client secretsConfigEncrypted columnOAuth flowAES-256
Webhook SecretsConfigurationwebhook_configsHMAC signingSHA-256 hashed
User CredentialsRegistrationusersAuth onlybcrypt hashed
API KeysGenerationapi_keysNever transmittedSHA-256 hashed

External Integrations

Summary of all external services Ledly integrates with:

CRM Integrations

IntegrationProtocolAuthenticationData Sent
HubSpotHTTPS REST APIOAuth 2.0Leads as Contacts
Microsoft Dynamics 365HTTPS OData 4.0Azure AD OAuth 2.0Leads
SalesforceHTTPS REST APIOAuth 2.0Leads

Email Services

IntegrationProtocolAuthenticationData Sent
ResendHTTPS REST APIAPI KeyTransactional emails (password reset, notifications)

Customer Webhooks

IntegrationProtocolAuthenticationData Sent
Custom EndpointsHTTPS POSTHMAC-SHA256 signatureLead events (JSON payload)
ZapierHTTPS POSTSignatureLead events
Make (Integromat)HTTPS POSTSignatureLead events

Infrastructure

ServicePurposeData Processed
AWS RDSPostgreSQL databaseAll persistent data
AWS S3File storageExports, imports
VercelApplication hostingRequest/response data

All external integrations use HTTPS with TLS 1.2 or higher. No sensitive data is sent to analytics or third-party tracking services.


Data Retention

Ledly retains data according to the following policies:

Default Retention Periods

Data TypeRetention PeriodDeletion Method
Lead dataUntil deleted by customerHard delete
Webhook delivery logs30 daysAutomatic purge
API request logs90 daysAutomatic purge
Audit logs90 daysAutomatic purge
User sessions24 hours (JWT expiry)Automatic expiry
Soft-deleted leads30 daysPermanent deletion

Data Deletion Procedures

Lead Deletion:

  1. Lead marked as deleted_at (soft delete)
  2. Lead excluded from all queries and exports
  3. After 30 days, permanently removed from database
  4. CRM record updated or deleted (configurable)

Account Deletion:

  1. All leads hard deleted
  2. All API keys revoked
  3. CRM connections disconnected
  4. Webhook endpoints removed
  5. User accounts deleted
  6. Organization record anonymized and retained for billing history

Right to Erasure (GDPR/CCPA):

  1. Submit deletion request via Settings or [email protected]
  2. Verification of request ownership
  3. 72-hour processing window
  4. Confirmation email sent upon completion
  5. Data deleted from all backups within 30 days

Backup Retention

Backup TypeRetentionEncryption
Daily snapshots7 daysAES-256
Weekly snapshots4 weeksAES-256
Monthly snapshots12 monthsAES-256
⚠️

Data deleted from the primary database may persist in backups for up to 30 days. For GDPR erasure requests, backup data is purged within this window.


Security Controls Summary

LayerControlImplementation
TransportEncryption in transitTLS 1.3 (minimum TLS 1.2)
StorageEncryption at restAES-256 (AWS RDS)
ApplicationAuthenticationJWT tokens, API keys
ApplicationAuthorizationRole-based access control
DatabaseTenant isolationRow-level security policies
APIRate limiting100 requests/10 seconds default
WebhooksRequest integrityHMAC-SHA256 signatures
WebhooksReplay protection5-minute timestamp validation
CredentialsPassword storagebcrypt with salt
CredentialsAPI key storageSHA-256 hash
CredentialsOAuth tokensAES-256 encryption

ResourceLink
API Authentication/api-reference/authentication
Webhook Configuration/integrations/webhooks
HubSpot Integration/integrations/hubspot
Dynamics 365 Integration/integrations/dynamics

Questions?

For security inquiries or to request a SOC 2 Type II report, contact: