# Get current user info
Source: https://developer.salesforge.ai/api-reference/auth/get-current-user-info
https://api.salesforge.ai/public/v2/swagger/doc.json get /me
Validates the provided API key and returns basic information about it.
# Bulk create contacts
Source: https://developer.salesforge.ai/api-reference/contacts/bulk-create-contacts
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/contacts/bulk
Create up to 100 contacts in bulk. Each contact requires firstName, at least one of email or linkedinUrl, and at least one tag (tags or tagIds). The request fails entirely if any contact is invalid.
# Create contact
Source: https://developer.salesforge.ai/api-reference/contacts/create-contact
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/contacts
Create a new contact. Requires at least one of email or linkedinUrl. If no tag is provided, a default date-based tag is applied automatically.
# Get contact
Source: https://developer.salesforge.ai/api-reference/contacts/get-contact
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/contacts/{contactID}
Get contact information.
# Get contacts
Source: https://developer.salesforge.ai/api-reference/contacts/get-contacts
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/contacts
Get contacts associated with the workspace.
# Get Custom Variables
Source: https://developer.salesforge.ai/api-reference/custom-vars/get-custom-variables
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/custom-vars
Get a list of custom variables associated to a workspace.
# Bulk create DNCs
Source: https://developer.salesforge.ai/api-reference/dnc/bulk-create-dncs
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/dnc/bulk
Create multiple DNC entries for a workspace.
# Get DNCs
Source: https://developer.salesforge.ai/api-reference/dnc/get-dncs
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/dnc
Get a paginated list of DNC entries for a workspace. Use this to sync a full DNC list into an external system.
# Create Email Reply
Source: https://developer.salesforge.ai/api-reference/email/create-email-reply
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/mailboxes/{mailboxID}/emails/{emailID}/reply
Create a new Email Reply.
# Download thread email attachment by content ID
Source: https://developer.salesforge.ai/api-reference/email/download-thread-email-attachment-by-content-id
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/mailboxes/{mailboxID}/emails/{emailID}/attachments/{contentID}
Streams a single attachment by content ID.
# Download thread email attachments
Source: https://developer.salesforge.ai/api-reference/email/download-thread-email-attachments
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/mailboxes/{mailboxID}/emails/{emailID}/attachments
Downloads all attachments of a thread email as a ZIP file.
# Create LinkedIn reply
Source: https://developer.salesforge.ai/api-reference/linkedin/create-linkedin-reply
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/threads/{threadID}/linkedin/reply
Sends a LinkedIn reply for the contact associated with the thread.
# Download LinkedIn message attachment
Source: https://developer.salesforge.ai/api-reference/linkedin/download-linkedin-message-attachment
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/threads/{threadID}/linkedin/messages/{messageID}/attachments/{attachmentID}
Streams a single LinkedIn attachment by attachment ID.
# Download LinkedIn message attachments
Source: https://developer.salesforge.ai/api-reference/linkedin/download-linkedin-message-attachments
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/threads/{threadID}/linkedin/messages/{messageID}/attachments
Downloads all attachments for a LinkedIn message as a ZIP archive.
# Connect mailbox
Source: https://developer.salesforge.ai/api-reference/mailboxes/connect-mailbox
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/mailboxes
Connect an SMTP/IMAP mailbox to the workspace. Send the outgoing server credentials under "smtp" and the incoming server credentials under "imap". Both are verified against the mail provider before anything is stored, so a bad host, port or password fails with 400 and creates nothing; the message names the failing protocol and, where the reason is recognized (wrong credentials, unreachable host, TLS mismatch, ...), a short cause. On success the mailbox is returned with status "pending": registration with the mail infrastructure finishes moments later and flips it to "active", or to "access_lost" if it fails. Poll GET /workspaces/{workspaceID}/mailboxes/{mailboxID} to observe the final status. Connecting is idempotent by address: a second request for an address already mid-connect returns 409 instead of racing the first. Google and Outlook mailboxes connect through /mailboxes/oauth-link instead.
# Create mailbox OAuth link
Source: https://developer.salesforge.ai/api-reference/mailboxes/create-mailbox-oauth-link
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/mailboxes/oauth-link
Returns a Google or Outlook OAuth URL for connecting a mailbox. After the user authorizes, they are redirected to redirectUrl. The mailbox is auto-provisioned in Warmforge under the matching workspace.
# Get mailbox
Source: https://developer.salesforge.ai/api-reference/mailboxes/get-mailbox
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/mailboxes/{mailboxID}
Get mailbox information.
# Get mailboxes
Source: https://developer.salesforge.ai/api-reference/mailboxes/get-mailboxes
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/mailboxes
Get mailboxes associated with the workspace.
# Update mailbox
Source: https://developer.salesforge.ai/api-reference/mailboxes/update-mailbox
https://api.salesforge.ai/public/v2/swagger/doc.json patch /workspaces/{workspaceID}/mailboxes/{mailboxID}
Update mailbox operational settings.
# Add enrollments
Source: https://developer.salesforge.ai/api-reference/multichannel/add-enrollments
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments
Deprecated. Enrolls matching contacts immediately without conflict review. Use the enrollment preflight and confirmation endpoints for new integrations.
# Assign subsequence to parent sequence
Source: https://developer.salesforge.ai/api-reference/multichannel/assign-subsequence-to-parent-sequence
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/subsequence-assignments
# Confirm enrollment preflight
Source: https://developer.salesforge.ai/api-reference/multichannel/confirm-enrollment-preflight
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight/{preflightID}/confirm
Applies a skip or move decision to a saved preflight. For each selected source, a draft enrollment is deleted; any other enrollment is set to completed and its pending steps are canceled. skipReplied is required for move decisions; true leaves replied contacts unenrolled. Contacts suppressed elsewhere by do-not-contact, unsubscribe, or bounce shield are enrolled with that status, are not contacted, and remain included in enrolledLeadIds and enrolledCount. A stale preflight returns 409 with a replacement preflight. A concurrent confirmation returns 423.
# Connect LinkedIn account
Source: https://developer.salesforge.ai/api-reference/multichannel/connect-linkedin-account
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/linkedin/accounts
Connects a LinkedIn account to the workspace using credentials and an optional proxy. A matching sender profile is created automatically. If LinkedIn issues a 2FA challenge, the response carries `requires2fa: true` and the caller must submit the code via the OTP endpoint.
# Create action node
Source: https://developer.salesforge.ai/api-reference/multichannel/create-action-node
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions
# Create condition node
Source: https://developer.salesforge.ai/api-reference/multichannel/create-condition-node
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/conditions
# Create sequence
Source: https://developer.salesforge.ai/api-reference/multichannel/create-sequence
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences
# Create subsequence trigger
Source: https://developer.salesforge.ai/api-reference/multichannel/create-subsequence-trigger
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/subsequences/{subsequenceID}/triggers
# Delete node
Source: https://developer.salesforge.ai/api-reference/multichannel/delete-node
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json delete /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}
# Delete sender profile
Source: https://developer.salesforge.ai/api-reference/multichannel/delete-sender-profile
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json delete /multichannel/workspaces/{workspaceID}/sender-profiles/{senderProfileID}
# Delete sequence
Source: https://developer.salesforge.ai/api-reference/multichannel/delete-sequence
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json delete /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}
# Get LinkedIn account
Source: https://developer.salesforge.ai/api-reference/multichannel/get-linkedin-account
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/linkedin/accounts/{linkedinAccountID}
Returns the current state of a LinkedIn account. Useful while polling a connection that is waiting on OTP.
# Get node
Source: https://developer.salesforge.ai/api-reference/multichannel/get-node
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}
# Get sequence details
Source: https://developer.salesforge.ai/api-reference/multichannel/get-sequence-details
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}
# Get sequence schedule
Source: https://developer.salesforge.ai/api-reference/multichannel/get-sequence-schedule
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule
# Get sequence settings
Source: https://developer.salesforge.ai/api-reference/multichannel/get-sequence-settings
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/settings
# Get validation run results
Source: https://developer.salesforge.ai/api-reference/multichannel/get-validation-run-results
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/validations/{runID}/results
# Launch sequence
Source: https://developer.salesforge.ai/api-reference/multichannel/launch-sequence
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/launch
# List actions
Source: https://developer.salesforge.ai/api-reference/multichannel/list-actions
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/actions
# List conditions
Source: https://developer.salesforge.ai/api-reference/multichannel/list-conditions
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/conditions
# List nodes
Source: https://developer.salesforge.ai/api-reference/multichannel/list-nodes
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes
# List sender profiles
Source: https://developer.salesforge.ai/api-reference/multichannel/list-sender-profiles
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sender-profiles
# List sequence branches
Source: https://developer.salesforge.ai/api-reference/multichannel/list-sequence-branches
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches
# List sequence sender profiles
Source: https://developer.salesforge.ai/api-reference/multichannel/list-sequence-sender-profiles
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles
# List sequences
Source: https://developer.salesforge.ai/api-reference/multichannel/list-sequences
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences
# List subsequence members
Source: https://developer.salesforge.ai/api-reference/multichannel/list-subsequence-members
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/subsequences/{subsequenceID}/members
Returns current multichannel subsequence members with enrollment and latest handoff timestamps. Use leadId to check one contact without changing state.
# List subsequence parents
Source: https://developer.salesforge.ai/api-reference/multichannel/list-subsequence-parents
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/subsequences/{subsequenceID}/parents
Returns every active parent-sequence assignment for a multichannel subsequence.
# List subsequence triggers
Source: https://developer.salesforge.ai/api-reference/multichannel/list-subsequence-triggers
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/subsequences/{subsequenceID}/triggers
# Preflight enrollments
Source: https://developer.salesforge.ai/api-reference/multichannel/preflight-enrollments
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight
Analyzes matching contacts before enrollment and creates a preflight that expires after 15 minutes. Returns candidate totals, conflicts, available source cleanup groups, and replied-contact information. validationRunId must reference a completed validation run containing at least one contact.
# Preview enrollment move
Source: https://developer.salesforge.ai/api-reference/multichannel/preview-enrollment-move
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight/{preflightID}/move-preview
Calculates the projected outcome of a move decision without changing enrollments. Returns projected enrollment, skip, and source cleanup counts with a skip-reason breakdown. A stale preflight returns 409 with a replacement preflight.
# Remove enrollments
Source: https://developer.salesforge.ai/api-reference/multichannel/remove-enrollments
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/remove
Removes matching contacts from the sequence. Enrollment preflight is not required.
# Remove sequence sender profiles
Source: https://developer.salesforge.ai/api-reference/multichannel/remove-sequence-sender-profiles
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles/remove
# Sequence email analytics (date range)
Source: https://developer.salesforge.ai/api-reference/multichannel/sequence-email-analytics-date-range
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json get /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/analytics
Returns per-day and aggregate email sending and engagement metrics for a multichannel sequence. Query dates are interpreted in `timezone` when provided (IANA name), otherwise UTC. Open and click metrics are zeroed when the sequence has those tracking modes disabled.
# Start validation run
Source: https://developer.salesforge.ai/api-reference/multichannel/start-validation-run
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/validations
Starts email validation for the contacts the filters resolve to.
When the filters match contacts but every one is filtered out before validation, for
example by a selectionScope of in_sequence or not_in_sequence or by excludedLeadIds,
the default response is 400 with data.code "validation-all-contacts-excluded". Send
"strict": false to receive 201 with an already completed empty run instead, where
"started" is false, "selected" is 0 and "message" explains why. A scope that resolves
to no contact with an email address returns 400 with data.code
"validation-scope-empty" either way. 402 means insufficient credits.
# Submit LinkedIn account OTP
Source: https://developer.salesforge.ai/api-reference/multichannel/submit-linkedin-account-otp
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/linkedin/accounts/{linkedinAccountID}/otp
Submits a one-time password to complete a LinkedIn account connection that is waiting on 2FA verification.
# Update action node
Source: https://developer.salesforge.ai/api-reference/multichannel/update-action-node
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions/{nodeID}
# Update sender profile
Source: https://developer.salesforge.ai/api-reference/multichannel/update-sender-profile
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sender-profiles/{senderProfileID}
# Update sequence
Source: https://developer.salesforge.ai/api-reference/multichannel/update-sequence
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}
# Update sequence schedule
Source: https://developer.salesforge.ai/api-reference/multichannel/update-sequence-schedule
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json put /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule
# Update sequence settings
Source: https://developer.salesforge.ai/api-reference/multichannel/update-sequence-settings
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/settings
# Update sequence status
Source: https://developer.salesforge.ai/api-reference/multichannel/update-sequence-status
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json patch /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/status
# Upsert sequence sender profiles
Source: https://developer.salesforge.ai/api-reference/multichannel/upsert-sequence-sender-profiles
https://multichannel-api.salesforge.ai/public/multichannel/swagger/doc.json post /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles
# Create Product
Source: https://developer.salesforge.ai/api-reference/products/create-product
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/products
Create a new product.
# Get product
Source: https://developer.salesforge.ai/api-reference/products/get-product
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/products/{productID}
Get product information.
# Get products
Source: https://developer.salesforge.ai/api-reference/products/get-products
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/products
Get products associated with the workspace.
# Assign contacts
Source: https://developer.salesforge.ai/api-reference/sequences/assign-contacts
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/contacts
Assign contacts to a sequence.
# Assign mailboxes
Source: https://developer.salesforge.ai/api-reference/sequences/assign-mailboxes
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/mailboxes
Assign mailboxes to a sequence.
# Confirm validation results
Source: https://developer.salesforge.ai/api-reference/sequences/confirm-validation-results
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/validation/confirm
Confirm validation results for contacts assigned to a sequence.
# Create sequence
Source: https://developer.salesforge.ai/api-reference/sequences/create-sequence
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/sequences
Create a new sequence.
# Delete sequence
Source: https://developer.salesforge.ai/api-reference/sequences/delete-sequence
https://api.salesforge.ai/public/v2/swagger/doc.json delete /workspaces/{workspaceID}/sequences/{sequenceID}
Delete a sequence.
# Get sequence analytics
Source: https://developer.salesforge.ai/api-reference/sequences/get-sequence-analytics
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequences/{sequenceID}/analytics
Get analytics for a sequence.
# Get sequence by ID
Source: https://developer.salesforge.ai/api-reference/sequences/get-sequence-by-id
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequences/{sequenceID}
Get a specific sequence by ID.
# Get sequence contact sending data
Source: https://developer.salesforge.ai/api-reference/sequences/get-sequence-contact-sending-data
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sending-data
Get sending data for sequences in a workspace.
# Get sequence contact validation results
Source: https://developer.salesforge.ai/api-reference/sequences/get-sequence-contact-validation-results
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/validation/result
Get sequence contact validation results.
# Get sequence contacts count
Source: https://developer.salesforge.ai/api-reference/sequences/get-sequence-contacts-count
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/count
Get the number of contacts assigned to a sequence.
# Get workspace sequence metrics
Source: https://developer.salesforge.ai/api-reference/sequences/get-workspace-sequence-metrics
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequence-metrics
Get sequence metrics associated with the workspace.
# Get workspace sequences
Source: https://developer.salesforge.ai/api-reference/sequences/get-workspace-sequences
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/sequences
Returns every sequence in the workspace. Multichannel sequences are listed first, followed by legacy sequences to fill the requested page size.
# Import lead
Source: https://developer.salesforge.ai/api-reference/sequences/import-lead
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/import-lead
Import lead to a sequence. Requires at least one of email or linkedinUrl. If no tag is provided, a default date-based tag is applied automatically.
# Skip validation results
Source: https://developer.salesforge.ai/api-reference/sequences/skip-validation-results
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/validation/skip
Skip validation results for contacts assigned to a sequence.
# Start sequence contact validation
Source: https://developer.salesforge.ai/api-reference/sequences/start-sequence-contact-validation
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/validation/start
Start sequence contact validation.
# Update sequence
Source: https://developer.salesforge.ai/api-reference/sequences/update-sequence
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}
Update an existing sequence.
# Update sequence schedules
Source: https://developer.salesforge.ai/api-reference/sequences/update-sequence-schedules
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/schedules
Update an existing sequence's schedules.
# Update sequence status
Source: https://developer.salesforge.ai/api-reference/sequences/update-sequence-status
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/status
Update a sequence's status.
# Update sequence steps
Source: https://developer.salesforge.ai/api-reference/sequences/update-sequence-steps
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/sequences/{sequenceID}/steps
Update an existing sequence's steps.
# Validate sequence contacts
Source: https://developer.salesforge.ai/api-reference/sequences/validate-sequence-contacts
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/sequences/{sequenceID}/contacts/validation/validate
Validate contacts assigned to a sequence.
# Get thread
Source: https://developer.salesforge.ai/api-reference/threads/get-thread
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/mailboxes/{mailboxID}/threads/{threadID}
Get thread information.
Deprecated: use GET /workspaces/{workspaceID}/threads/{threadID} instead, which also supports mailbox-less (LinkedIn-only) threads.
# Get thread
Source: https://developer.salesforge.ai/api-reference/threads/get-thread-1
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/threads/{threadID}
Get thread information for a workspace thread. Supports both mailbox (email)
threads and mailbox-less (LinkedIn-only) threads created through multichannel
sequence execution.
# Get workspace primebox labels
Source: https://developer.salesforge.ai/api-reference/threads/get-workspace-primebox-labels
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/primebox-labels
Get a paginated list of primebox labels associated with the workspace.
# Get workspace threads
Source: https://developer.salesforge.ai/api-reference/threads/get-workspace-threads
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/threads
Get a list of threads associated with the workspace.
# Update thread label
Source: https://developer.salesforge.ai/api-reference/threads/update-thread-label
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/mailboxes/{mailboxID}/threads/{threadID}/label
Update the label of a thread.
Deprecated: use PUT /workspaces/{workspaceID}/threads/{threadID}/label instead, which also supports mailbox-less (LinkedIn-only) threads.
# Update thread label
Source: https://developer.salesforge.ai/api-reference/threads/update-thread-label-1
https://api.salesforge.ai/public/v2/swagger/doc.json put /workspaces/{workspaceID}/threads/{threadID}/label
Update the label of a workspace thread. Supports both mailbox (email) threads
and mailbox-less (LinkedIn-only) threads created through multichannel sequence execution.
# Create webhook
Source: https://developer.salesforge.ai/api-reference/webhooks/create-webhook
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces/{workspaceID}/integrations/webhooks
Create a new webhook.
# Delete webhook
Source: https://developer.salesforge.ai/api-reference/webhooks/delete-webhook
https://api.salesforge.ai/public/v2/swagger/doc.json delete /workspaces/{workspaceID}/integrations/webhooks/{webhookID}
Delete a registered webhook. A webhook's signing secret cannot be rotated or retrieved, so deleting and recreating the webhook is how a lost or leaked secret is replaced.
# Get webhook
Source: https://developer.salesforge.ai/api-reference/webhooks/get-webhook
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/integrations/webhooks/{webhookID}
Get webhook information.
# Get webhooks
Source: https://developer.salesforge.ai/api-reference/webhooks/get-webhooks
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}/integrations/webhooks
Get registered webhooks associated with the workspace.
# Create workspace
Source: https://developer.salesforge.ai/api-reference/workspaces/create-workspace
https://api.salesforge.ai/public/v2/swagger/doc.json post /workspaces
Create a new workspace for the account.
# Get workspace
Source: https://developer.salesforge.ai/api-reference/workspaces/get-workspace
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces/{workspaceID}
Get workspace information.
# Get workspaces
Source: https://developer.salesforge.ai/api-reference/workspaces/get-workspaces
https://api.salesforge.ai/public/v2/swagger/doc.json get /workspaces
Get workspaces associated with the account.
# Authentication
Source: https://developer.salesforge.ai/authentication
Authenticate with Salesforge public APIs using an API key in the Authorization header.
Salesforge public APIs use API key authentication.
Send the raw API key in the `Authorization` header. Do not add a `Bearer ` prefix.
```http theme={null}
Authorization: YOUR_API_KEY
```
Bearer authentication and API key authentication are different. Adding
`Bearer ` changes the header value and causes Salesforge requests to fail.
## How authentication works
1. Generate a Salesforge API key for the account or workspace you are integrating.
2. Send the raw key in the `Authorization` header on every request.
3. Handle standard auth failures such as missing, invalid, or out-of-scope keys.
Multichannel public endpoints accept both the raw key and the Bearer format.
Use the raw key because it works with both Salesforge and Multichannel endpoints.
## Request examples
The following examples use the raw Salesforge API key.
```bash theme={null}
curl -sS "$SALESFORGE_API_BASE_URL/your-endpoint" \
-H "Authorization: $SALESFORGE_API_KEY"
```
```javascript theme={null}
const response = await fetch(`${process.env.SALESFORGE_API_BASE_URL}/your-endpoint`, {
headers: {
Authorization: process.env.SALESFORGE_API_KEY
}
});
console.log(await response.json());
```
```go theme={null}
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest(
http.MethodGet,
os.Getenv("SALESFORGE_API_BASE_URL")+"/your-endpoint",
nil,
)
req.Header.Set("Authorization", os.Getenv("SALESFORGE_API_KEY"))
_, _ = http.DefaultClient.Do(req)
}
```
## Common authentication errors
| HTTP status | Typical reason |
| ----------- | ------------------------------------------------------------------- |
| 401 | Missing or invalid API key |
| 403 | API key is valid but does not have access to the requested resource |
## Next steps
* Apply [Security best practices](/authentication/security-best-practices).
# Security best practices
Source: https://developer.salesforge.ai/authentication/security-best-practices
Protect API keys and reduce integration risk in production.
Follow these practices for production integrations.
## Protect secrets
* Store API keys in a secrets manager.
* Restrict secret access by environment and role.
* Avoid logging full keys.
## Limit blast radius
* Use separate keys for each environment and integration.
* Revoke compromised or unused keys immediately.
## Rotate keys
* Rotate on a fixed schedule.
* Rotate after team or vendor access changes.
* Test key rollover before revoking the old key.
## Validate failures safely
When troubleshooting auth failures:
* Check header format first.
* Confirm product and endpoint access requirements.
* Avoid sharing raw keys in tickets or chat.
# Overview
Source: https://developer.salesforge.ai/index
Build with Salesforge APIs using verified, workflow-first documentation.
Salesforge is an AI-powered revenue technology company building the infrastructure, software, and intelligence layer behind modern outbound sales.
Through its ecosystem of products known as the Forge Stack, Salesforge enables companies to find prospects, build and manage email infrastructure, improve deliverability, automate outreach, and scale pipeline generation with AI.
The company's flagship platform, Salesforge, helps sales teams, agencies, and revenue organizations automate personalized outbound campaigns using AI-driven prospecting, email generation, multichannel sequencing, and autonomous sales workflows. At the center of this vision is Agent Frank, an AI SDR designed to assist with prospecting, personalization, and pipeline generation.
The broader Forge Stack provides the foundational infrastructure required to operate outbound sales at scale:
*
[Salesforge](https://salesforge.ai) helps teams automate AI-powered outreach and multichannel sequencing.
*
[Mailforge](https://mailforge.ai) provides shared cold email infrastructure with automated domain, mailbox, and DNS configuration.
*
[Warmforge](https://warmforge.ai) improves inbox placement through mailbox warming, deliverability monitoring, placement testing, and reputation management.
*
[Infraforge](https://infraforge.ai) offers private email infrastructure with dedicated resources for organizations requiring maximum control and scalability.
*
[Primeforge](https://primeforge.ai) delivers premium Google Workspace and Microsoft 365 mailbox infrastructure optimized for deliverability.
*
[Leadsforge](https://leadsforge.ai) helps teams discover and qualify prospects.
This documentation focuses on helping you adopt Salesforge products quickly with practical guides, clear API endpoint docs, and implementation tutorials.
## Start here
Learn the shared authentication pattern used across Forge product APIs.
Protect API keys, rotate credentials safely, and reduce integration risk.
Build a multichannel sequence end to end, from contact creation through launch.
Start with the auth requirements used by Salesforge and multichannel endpoint references.
# Client setup guides
Source: https://developer.salesforge.ai/mcp/client-setup
Configure the Forge MCP server in Claude, Cursor, Windsurf, VS Code, and other MCP clients.
This page covers setup for multiple MCP clients. You can use one or more product keys, depending on which Forge tools you need.
## Claude Desktop
1. Open Claude Desktop.
2. Go to Settings -> Developer -> Edit Config.
3. Add this server configuration:
```json theme={null}
{
"mcpServers": {
"salesforge": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.salesforge.ai/mcp",
"--header",
"X-Salesforge-Key:YOUR_SALESFORGE_API_KEY",
"--header",
"X-Primeforge-Key:YOUR_PRIMEFORGE_API_KEY",
"--header",
"X-Leadsforge-Key:YOUR_LEADSFORGE_API_KEY",
"--header",
"X-Infraforge-Key:YOUR_INFRAFORGE_API_KEY",
"--header",
"X-Warmforge-Key:YOUR_WARMFORGE_API_KEY",
"--header",
"X-Mailforge-Key:YOUR_MAILFORGE_API_KEY"
]
}
}
}
```
4. Remove header lines for products you do not use.
5. Save the file and restart Claude Desktop.
## Claude Code
Run:
```bash theme={null}
claude mcp add salesforge \
--transport streamable-http \
--url https://mcp.salesforge.ai/mcp \
--header "X-Salesforge-Key:YOUR_SALESFORGE_API_KEY" \
--header "X-Primeforge-Key:YOUR_PRIMEFORGE_API_KEY" \
--header "X-Leadsforge-Key:YOUR_LEADSFORGE_API_KEY" \
--header "X-Infraforge-Key:YOUR_INFRAFORGE_API_KEY" \
--header "X-Warmforge-Key:YOUR_WARMFORGE_API_KEY" \
--header "X-Mailforge-Key:YOUR_MAILFORGE_API_KEY"
```
Only keep headers for products you actually use.
For more detailed Claude Code connection guidance and screenshots, see the Salesforge help article:
[Salesforge MCP server with Claude for cold Email and LinkedIn outreach](https://help.salesforge.ai/en/articles/10333582-salesforge-mcp-server-with-claude-for-cold-email-and-linkedin-outreach)
## Cursor
In Cursor, open Settings -> MCP and add a server with:
* Name: `salesforge`
* Type: `streamable-http`
* URL: `https://mcp.salesforge.ai/mcp`
* Headers: product headers from [MCP overview](/mcp/overview)
## Windsurf and other MCP clients
For clients that support direct remote MCP server configuration, use:
```json theme={null}
{
"mcpServers": {
"salesforge": {
"url": "https://mcp.salesforge.ai/mcp",
"headers": {
"X-Salesforge-Key": "YOUR_SALESFORGE_API_KEY",
"X-Primeforge-Key": "YOUR_PRIMEFORGE_API_KEY",
"X-Leadsforge-Key": "YOUR_LEADSFORGE_API_KEY",
"X-Infraforge-Key": "YOUR_INFRAFORGE_API_KEY",
"X-Warmforge-Key": "YOUR_WARMFORGE_API_KEY",
"X-Mailforge-Key": "YOUR_MAILFORGE_API_KEY"
}
}
}
}
```
If your client does not support direct remote MCP yet, use the `npx mcp-remote` bridge pattern shown in the Claude Desktop section.
## VS Code MCP clients
If you use a VS Code MCP extension, configure the same endpoint and headers:
* URL: `https://mcp.salesforge.ai/mcp`
* Transport: `streamable-http`
* Headers: only the product headers you use
Use your extension's MCP settings or JSON config format. If command-based configuration is required, use `npx mcp-remote` with the same headers.
## Verify the connection
After setup, test prompts like:
* "List my Salesforge workspaces"
* "Show my Primeforge domains"
* "Search Leadsforge for CTOs at SaaS companies"
* "Show my Warmforge warmup stats"
* "List my Mailforge domains"
# MCP overview
Source: https://developer.salesforge.ai/mcp/overview
Connect Salesforge products to MCP-compatible AI clients.
Use the Forge MCP server to connect Salesforge products to MCP-compatible AI clients such as Claude Desktop, Claude Code, Cursor, Windsurf, and VS Code MCP extensions.
## MCP endpoint
* URL: `https://mcp.salesforge.ai/mcp`
* Transport: `streamable-http`
* Authentication: product-specific headers
## Prerequisites
1. API access for one or more Forge products.
2. An MCP-compatible client.
3. Node.js installed if your client uses an `npx` bridge command.
## Get product API keys
Generate keys in each product app you want to use:
| Product | Where to generate key |
| ---------- | ----------------------------------------------------------------------------- |
| Salesforge | [Go to Salesforge](https://app.salesforge.ai) -> `Settings Page` -> `API tab` |
| Primeforge | [Go to Primeforge](https://app.primeforge.ai) -> `Settings Page` -> `API tab` |
| Leadsforge | [Go to Leadsforge](https://app.leadsforge.ai) -> `Settings Page` -> `API tab` |
| Infraforge | [Go to Infraforge](https://app.infraforge.ai) -> `Settings Page` -> `API tab` |
| Warmforge | [Go to Warmforge](https://app.warmforge.ai) -> `Settings Page` -> `API tab` |
| Mailforge | [Go to Mailforge](https://app.mailforge.ai) -> `Settings Page` -> `API tab` |
## Authentication headers
| Product | Header |
| ---------- | ------------------ |
| Salesforge | `X-Salesforge-Key` |
| Primeforge | `X-Primeforge-Key` |
| Leadsforge | `X-Leadsforge-Key` |
| Infraforge | `X-Infraforge-Key` |
| Warmforge | `X-Warmforge-Key` |
| Mailforge | `X-Mailforge-Key` |
Only include headers for products you use. Tools for unconfigured products do not appear.
## What you can do through MCP
* Work with Salesforge workspaces, contacts, sequences, nodes, branches, enrollments, sender profiles, and webhooks.
* Work with Primeforge, Infraforge, Warmforge, Leadsforge, and Mailforge from the same MCP endpoint.
* Run multichannel workflows (LinkedIn + email) from one conversational interface.
## Next steps
* Follow [Client setup guides](/mcp/client-setup).
* Use [Troubleshooting](/mcp/troubleshooting) if tools do not appear or auth fails.
## References
* [Forge MCP GitHub repository](https://github.com/SalesforgeAI/forge-mcp)
* [Salesforge help article: MCP server with Claude, Cursor, Windsurf, and other clients](https://help.salesforge.ai/en/articles/10333582-salesforge-mcp-server-with-claude-for-cold-email-and-linkedin-outreach)
# MCP troubleshooting
Source: https://developer.salesforge.ai/mcp/troubleshooting
Fix common setup and authentication issues when connecting Forge MCP tools.
Use this checklist when tools do not appear or requests fail.
## Tools are not showing up
* Restart your MCP client after changing configuration.
* Confirm at least one valid product key is set.
* Confirm your server URL is exactly `https://mcp.salesforge.ai/mcp`.
## Authentication errors
* Verify the correct header name per product.
* Verify each key value is correct and active.
* In command-based configs, keep header format exact: `X-Salesforge-Key:YOUR_KEY`.
## Missing product tools
Tools only appear for products with valid keys configured. Example:
* No `X-Primeforge-Key` -> no Primeforge tools.
* No `X-Warmforge-Key` -> no Warmforge tools.
## Invalid workspace errors
Some tools require a workspace ID. First run a workspace-listing prompt and reuse a valid ID.
## Multiple accounts
If you manage multiple client accounts, add multiple MCP server entries with different names and keys:
```json theme={null}
{
"mcpServers": {
"salesforge-client-a": {
"url": "https://mcp.salesforge.ai/mcp",
"headers": {
"X-Salesforge-Key": "CLIENT_A_SALESFORGE_KEY"
}
},
"salesforge-client-b": {
"url": "https://mcp.salesforge.ai/mcp",
"headers": {
"X-Salesforge-Key": "CLIENT_B_SALESFORGE_KEY"
}
}
}
}
```
Add extra product headers in each entry when needed.
## More setup details
* [Client setup guides](/mcp/client-setup)
* [MCP overview](/mcp/overview)
* [Salesforge help article](https://help.salesforge.ai/en/articles/10333582-salesforge-mcp-server-with-claude-for-cold-email-and-linkedin-outreach)
* [Forge MCP GitHub repository](https://github.com/SalesforgeAI/forge-mcp)
# Mailbox
Source: https://developer.salesforge.ai/overview/mailbox
Understand mailbox as a shared delivery concept across Forge products.
A mailbox is the sending and receiving identity used for outbound execution and reply handling.
Across Forge products, mailbox setup and health directly affect delivery quality and campaign stability.
## Why mailbox matters
* Mailboxes are tied to sender identity and reputation.
* Sequence execution depends on valid mailbox assignment.
* Warmup and placement behavior affect inbox outcomes.
## Mailbox across Forge products
| Product area | Typical mailbox usage |
| ------------------------ | --------------------------------------------------------- |
| Salesforge | Use mailboxes for sequence sending and reply workflows |
| Primeforge | Provision and manage deliverability-focused mailboxes |
| Mailforge and Infraforge | Manage mailbox infrastructure with domain and DNS context |
| Warmforge | Track warmup and placement metrics for mailbox health |
## Operational checklist
1. Confirm mailbox is configured before enrollment and launch flows.
2. Assign the right mailbox to the right workspace and sender context.
3. Monitor warmup and placement performance continuously.
4. Reduce or rebalance sending when mailbox health drops.
## Related resources
* [Workspace concept](/overview/workspace)
* [Salesforge API authentication](/salesforge-api/api-endpoints)
* [MCP client setup guides](/mcp/client-setup)
# Workspace
Source: https://developer.salesforge.ai/overview/workspace
Understand workspace as a core scope concept across Forge products.
A workspace is the core organizational scope used across Forge products and APIs.
You use workspace context to separate teams, resources, and outbound operations.
## Why workspace matters
* Access control is usually evaluated at workspace level.
* Many API operations and MCP tools are workspace-scoped.
* Contacts, sequences, sender profiles, and related resources are typically managed per workspace.
## Workspace in day-to-day integrations
1. Resolve the target workspace before running writes.
2. Persist workspace IDs in your integration state.
3. Pass workspace IDs on workspace-scoped API endpoints and MCP tools.
4. Handle workspace authorization failures (for example 403) as scope errors, not transport errors.
## Common mistakes to avoid
* Reusing a workspace ID from one account in another account context.
* Assuming a valid API key can access every workspace.
* Running write operations before confirming workspace scope.
## Related resources
* [Mailbox concept](/overview/mailbox)
* [Authentication](/authentication)
* [Salesforge API authentication](/salesforge-api/api-endpoints)
* [MCP overview](/mcp/overview)
# Create a multichannel sequence
Source: https://developer.salesforge.ai/products/salesforge/create-multichannel-sequence
Create leads, build a multichannel sequence, enroll leads, and launch it using Salesforge and multichannel public APIs.
This tutorial walks through a full multichannel-sequence setup for a single workspace.
By the end of the flow, you will have:
1. Created three leads with different channel availability.
2. Created a multichannel sequence in draft state.
3. Added a LinkedIn branch and an email branch.
4. Optionally validated leads before enrollment.
5. Enrolled leads through preflight and confirmation.
6. Launched the sequence.
This tutorial assumes your sender profile already exists and is ready to use. The guide still covers how to list sender profiles and assign one to the sequence.
This is a two-service workflow.
* Use the Salesforge public API to create leads in the workspace.
* Use the multichannel public API to create the sequence, add nodes, validate leads, enroll them, and launch the sequence.
The endpoint paths below are shown as they appear in the public API references.
## Prerequisites
Prepare these values before you start:
* `WORKSPACE_ID`
* `SALESFORGE_API_KEY`
* `SENDER_PROFILE_ID`
* a base URL for the Salesforge public API
* a base URL for the multichannel public API
Salesforge public API and multichannel public API use the same API key.
Use `SALESFORGE_API_KEY` for requests to both services.
Send the raw API key in the `Authorization` header for both APIs. Multichannel
also accepts an optional `Bearer ` prefix, but Salesforge does not.
```http theme={null}
Authorization: YOUR_API_KEY
```
For the shared authentication pattern, see [Authentication](/authentication).
## Step 1: Create leads in the workspace
Start by creating the leads you want to target.
For this guide, create three lead shapes:
* one lead with email only,
* one lead with LinkedIn only,
* one lead with both email and LinkedIn.
Use the Salesforge public API bulk-create endpoint so you can create the full sample set in one request.
`POST /workspaces/{workspaceID}/contacts/bulk`
The Salesforge public API uses `contacts` in endpoint paths and response field names. In this guide, those same records are referred to as leads to keep the sequencing terminology consistent.
```json theme={null}
{
"contacts": [
{
"firstName": "Alicia",
"lastName": "North",
"email": "alicia.north@example.com",
"company": "Northwind Labs",
"position": "Revenue Operations Manager",
"tags": ["guide-multichannel", "email-only"],
"customVars": {
"segment": "SMB",
"industry": "Software"
}
},
{
"firstName": "Bruno",
"lastName": "Vale",
"linkedinUrl": "https://www.linkedin.com/in/bruno-vale",
"company": "Atlas Advisory",
"position": "Founder",
"tags": ["guide-multichannel", "linkedin-only"],
"customVars": {
"segment": "Founder-led"
}
},
{
"firstName": "Camila",
"lastName": "Stone",
"email": "camila.stone@example.com",
"linkedinUrl": "https://www.linkedin.com/in/camila-stone",
"company": "Signal Peak",
"position": "VP Sales",
"tags": ["guide-multichannel", "omnichannel"],
"customVars": {
"segment": "Mid-market",
"priority": "High"
}
}
]
}
```
This request uses the public lead-creation rules exposed by the contact endpoint:
* `firstName` is required.
* At least one of `email` or `linkedinUrl` must be present.
* Each lead must include at least one tag through `tags` or `tagIds`.
* `customVars` is optional, but it cannot be an empty object.
The response returns created leads under the `contacts` field, with fields such as `id`, `email`, `linkedinUrl`, `tags`, and `customVars`.
The action examples later in this guide use `{{segment}}` in message content, so make sure each lead you plan to enroll has that custom variable populated.
Store the returned IDs for later steps. In this tutorial, refer to them as:
* `EMAIL_ONLY_LEAD_ID`
* `LINKEDIN_ONLY_LEAD_ID`
* `OMNICHANNEL_LEAD_ID`
If you want to confirm the leads are available in the workspace, you can also query the workspace contact-list endpoint.
`GET /workspaces/{workspaceID}/contacts`
## Step 2: Discover the available multichannel actions and conditions
Do not hardcode action or condition IDs in client code.
Use the reference endpoints first, then resolve the IDs you need from the response.
### List email actions
`GET /multichannel/actions?channel=email`
The action catalog includes a name, channel, branching type, and description. For an email step, the action you usually want is the one whose `name` is `send_email`.
### List LinkedIn actions
`GET /multichannel/actions?channel=linkedin`
Common LinkedIn actions include these names:
* `li_connection_request`
* `li_send_message`
* `li_send_inmail`
* `li_view_profile`
* `li_withdraw_connection_request`
* `li_like_latest_post`
* `li_follow_profile`
For this tutorial, resolve the `id` for `li_send_message` and store it as `LINKEDIN_MESSAGE_ACTION_ID`.
### List available conditions
`GET /multichannel/conditions`
Use this endpoint to resolve the condition IDs you want to use in the graph.
For the sample flow below, resolve the condition whose `name` is `has_linkedin_url` and store its ID as `HAS_LINKEDIN_URL_CONDITION_ID`.
Other useful conditions you may see in the same catalog include:
* `has_email_address`
* `request_accepted_within_days`
* `li_is_already_connected`
* `li_contact_replied_within_days`
* `check_email_validation_status`
## Step 3: Create the multichannel sequence
Create the sequence in draft state first.
`POST /multichannel/workspaces/{workspaceID}/sequences`
```json theme={null}
{
"name": "Guide - LinkedIn then Email",
"description": "A sample multichannel sequence that routes leads into LinkedIn or email outreach.",
"timezone": "America/New_York",
"settings": {
"openTrackingEnabled": true,
"plainTextEmailsEnabled": true,
"optOutLinkEnabled": true,
"optOutLinkText": "Unsubscribe",
"trackOpportunitiesEnabled": false
}
}
```
The create response returns the new sequence with fields such as:
* `id`
* `status`
* `timezone`
* `name`
* `description`
* `settings`
Store the returned `id` as `SEQUENCE_ID`.
`espMatchingEnabled` is part of the public settings model, but enabling it can be feature-gated by workspace plan. Keep it omitted or false unless you know the workspace supports it.
If you need to refine sequence metadata after creation, use the update endpoint.
`PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`
## Step 4: Set an explicit schedule
New sequences receive a default schedule, but production integrations should normally set the schedule explicitly.
`PUT /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule`
```json theme={null}
{
"timezone": "America/New_York",
"schedule": {
"monday": { "enabled": true, "from": 9, "to": 17 },
"tuesday": { "enabled": true, "from": 9, "to": 17 },
"wednesday": { "enabled": true, "from": 9, "to": 17 },
"thursday": { "enabled": true, "from": 9, "to": 17 },
"friday": { "enabled": true, "from": 9, "to": 17 },
"saturday": { "enabled": false },
"sunday": { "enabled": false }
}
}
```
The schedule model uses per-day `enabled`, `from`, and `to` values, where hours are integers from `0` to `23` and `to` must be greater than `from`.
To verify the schedule that is now attached to the sequence, call:
`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule`
## Step 5: Assign an existing sender profile
The sender profile is the execution identity for the sequence. It is where mailbox and LinkedIn execution context come together.
First, list sender profiles for the workspace if you need to confirm the correct profile ID.
`GET /multichannel/workspaces/{workspaceID}/sender-profiles`
Then assign the sender profile to the sequence.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles`
```json theme={null}
{
"senderProfileIds": [123]
}
```
Replace `123` with your actual sender profile ID or the `SENDER_PROFILE_ID` you already prepared.
To verify the assignment, call:
`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles`
## Step 6: Get the root branch before adding nodes
When a multichannel sequence is created, the root node is created automatically. You do not create that node yourself.
Before you can add an action or condition, you need the branch ID that leaves the root node.
`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`
Each branch record includes:
* `id`
* `fromNodeId`
* `toNodeId`
* `name`
* `description`
Store the branch ID that starts from the root node as `ROOT_BRANCH_ID`.
## Desired sequence shape
```mermaid theme={null}
flowchart TD
A[Root node] --> B{Has LinkedIn URL?}
B -->|Yes| C[LinkedIn message]
B -->|No| D[Email follow-up]
```
This is the sequence you are building in the next steps. The root node already exists when the sequence is created, then a condition node routes leads by LinkedIn availability: leads with a LinkedIn identity go to the LinkedIn message branch, while email-only leads go to the email branch.
## Step 7: Add the first condition node
This tutorial uses one condition node first so the sequence can route leads by channel availability.
Leads with a LinkedIn URL will go down the LinkedIn path. Leads without one will go down the email path.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/conditions`
```json theme={null}
{
"branchId": 1001,
"conditionId": 12,
"minutesToWait": 0,
"distributionStrategy": "equal"
}
```
Replace:
* `1001` with `ROOT_BRANCH_ID`
* `12` with `HAS_LINKEDIN_URL_CONDITION_ID`
The created node response returns the condition node itself. After that, list branches again.
`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`
The condition node creates outgoing branches that you can identify by `name`, typically `yes` and `no`.
Store them as:
* `LINKEDIN_BRANCH_ID` for the `yes` branch
* `EMAIL_BRANCH_ID` for the `no` branch
## Step 8: Add a LinkedIn action and an email action
Now create one action node on each branch.
### LinkedIn message action
Use the branch for leads that do have a LinkedIn URL.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions`
```json theme={null}
{
"branchId": 2001,
"actionId": 31,
"waitDays": 0,
"distributionStrategy": "equal",
"variants": [
{
"metadata": {
"name": "LinkedIn intro",
"message": "Hi {{first_name}}, I work with {{segment}} teams that want more control over multichannel outbound. Open to a short conversation?",
"subject": ""
},
"exposureInPercentage": 100,
"isEnabled": true
}
]
}
```
Replace:
* `2001` with `LINKEDIN_BRANCH_ID`
* `31` with `LINKEDIN_MESSAGE_ACTION_ID`
### Email action
Use the branch for leads that do not have a LinkedIn URL.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions`
```json theme={null}
{
"branchId": 2002,
"actionId": 14,
"waitDays": 1,
"distributionStrategy": "equal",
"variants": [
{
"metadata": {
"name": "Email follow-up",
"subject": "Quick intro for {{company}}",
"message": "Hi {{first_name}}, I wanted to share a simple way {{segment}} teams use multichannel workflows without splitting leads across separate systems.",
"allowed_validation_statuses": ["safe", "catch_all"]
},
"exposureInPercentage": 100,
"isEnabled": true
}
]
}
```
Replace:
* `2002` with `EMAIL_BRANCH_ID`
* `14` with the action ID whose `name` is `send_email`
This gives the sequence both channels while still handling mixed lead shapes in one graph.
If you want a lead to receive LinkedIn first and email later in the same branch, keep extending the graph by listing branches again after node creation, then creating new actions on the downstream branch IDs returned by the API.
## Step 9: Inspect the graph you just created
Before you validate or enroll leads, inspect the sequence structure.
Useful inspection endpoints are:
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`
These responses let you verify:
* the sequence is still in `draft` status,
* the correct sender profile is attached,
* the nodes are present with the right `actionId` or `conditionId`,
* the branch IDs and `yes` or `no` routing look correct.
## Step 10: Optionally run validation before enrollment
Validation is optional. Use it to check email addresses before enrollment or to select leads for a later preflight.
Validation in this flow is email-only. Use it for leads that have an email address, and add LinkedIn-only leads through a separate enrollment filter instead of putting them in the validation run.
Start the validation run first.
`POST /multichannel/workspaces/{workspaceID}/validations`
```json theme={null}
{
"filters": {
"leadIds": [
"EMAIL_ONLY_LEAD_ID",
"OMNICHANNEL_LEAD_ID"
]
},
"limit": 2
}
```
The create response returns `validationJobID`. Use that value as the run identifier in later steps.
To inspect the results, call:
`GET /multichannel/workspaces/{workspaceID}/validations/{runID}/results`
Wait until the validation run has `completed` status before using it for enrollment. You cannot use a pending or in-progress run. If the run fails, start another one.
Validation filters can be broader than `leadIds`. The API also supports filters such as:
* `tagIds`
* `esps`
* `validationStatuses`
* `customVars`
* `searchQuery`
* `hasValidLinkedIn`
* `hasEmail`
* `excludeContacted`
You can reuse the completed validation run when selecting leads for preflight.
## Step 11: Preflight and confirm enrollment
Use enrollment preflight for new integrations. It checks the selected leads before enrollment and shows whether any are already in another sequence or have replied.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight`
For the three sample leads in this tutorial, start with this request:
```json theme={null}
{
"filters": {
"leadIds": [
"EMAIL_ONLY_LEAD_ID",
"LINKEDIN_ONLY_LEAD_ID",
"OMNICHANNEL_LEAD_ID"
]
},
"limit": 3,
"selectionScope": "all"
}
```
The response returns a `preflightId`, an expiration time, enrollment counts, and any sequences that already contain the selected leads.
If these new sample leads are not in another sequence and have not replied, confirm with `action` set to `skip`. In this case, all three leads are enrolled:
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight/{preflightID}/confirm`
```json theme={null}
{
"action": "skip"
}
```
If some leads need a decision, you can leave them in their current sequences or move them into this one. See [Enroll contacts with preflight](/products/salesforge/enroll-contacts-with-preflight) for the complete workflow and error handling.
The direct add-enrollments endpoint is deprecated. Use the preflight and confirmation flow for new integrations.
## Step 12: Verify enrollments and launch the sequence
Before launch, re-check the sequence details.
`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`
The detailed response includes:
* `sequence`
* `nodes`
* `branches`
* `activeEnrollmentCount`
Once the graph, schedule, sender profile, and enrollments look correct, launch the sequence.
`PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/launch`
This endpoint does not require a request body. A successful response returns the sequence in its launched state, with status set to `active`.
Treat launch as the point where the sequence graph becomes locked. In the multichannel-api service, launched sequences cannot be structurally edited: creating nodes, deleting nodes, or rewiring branches is blocked after launch.
Operational updates still remain available through public endpoints. The code continues to allow adding new enrollments after launch, and schedule updates also remain available. Public update endpoints for sequence metadata/settings and sender-profile assignment are still exposed as well, so the important restriction here is specifically on changing the workflow structure after launch.
The response returns the sequence in its launched state. That is the point where the sequence can begin active execution.
## Step 13: Know the follow-up endpoints you will use next
After the first launch, these are the endpoints you will usually use operationally:
* `GET /multichannel/workspaces/{workspaceID}/sequences` to list sequences
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}` to inspect one sequence in detail
* `PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/status` to pause or resume a sequence
* `POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/remove` to remove leads by filter
* `PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions/{nodeID}` to update an action node
* `DELETE /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}` to remove a node
* `DELETE /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}` to delete the sequence
Those are maintenance endpoints. The create-to-launch flow stays the same: create leads, resolve action and condition IDs, create the sequence, set schedule, assign sender profile, build nodes, optionally validate, enroll, inspect, and launch.
## Related resources
* [Authentication](/authentication)
* [Salesforge API authentication](/salesforge-api/api-endpoints)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Enroll contacts with preflight](/products/salesforge/enroll-contacts-with-preflight)
* [Sender Profile](/salesforge-api/concepts/sender-profile)
# Enroll contacts with preflight
Source: https://developer.salesforge.ai/products/salesforge/enroll-contacts-with-preflight
Check contacts, choose how to handle existing sequence enrollments, and enroll them in a multichannel sequence.
Before you enroll contacts, create a preflight. A preflight checks whether the selected contacts are already in another sequence or have replied. It returns the available choices without changing any enrollments.
You then confirm one of two actions: skip contacts that need a decision, or move them into this sequence. If you plan to move contacts, you can preview the result before confirming.
The direct enrollment endpoint is deprecated. Use preflight and confirmation for new integrations.
## Prerequisites
Prepare these values before you start:
* `WORKSPACE_ID`
* `SEQUENCE_ID` for the sequence you want to enroll contacts into
* `SALESFORGE_API_KEY`
* the contact filters or contact IDs you want to use
Send the raw API key in the `Authorization` header:
```http theme={null}
Authorization: YOUR_API_KEY
```
For the shared authentication pattern, see [Authentication](/authentication).
## Select contacts
You must provide at least one contact filter or set `limit` to a number greater than zero.
* Contact IDs: `leadIds`, `notInLeadIds`
* Tag IDs: `tagIds`, `notInTagIds`
* Email service providers: `esps`, `notInESPs`
* Custom variable values: `customVars`, `notInCustomVars`
* Custom variable IDs: `customVarIds`, `notInCustomVarIds`
* Contact search: `searchQuery`
* Email validation: `validationStatuses`, `validationRunId`
* Contact state: `excludeContacted`, `hasValidLinkedIn`, `hasEmail`
Use `limit` to set the maximum number of contacts the preflight checks.
## Enrollment flow
```mermaid theme={null}
flowchart LR
A[Check contacts] --> B{Contacts need a decision?}
B -->|No| C[Confirm enrollment]
B -->|Yes, skip them| C
B -->|Yes, move them| D[Preview move]
D --> C
```
Create a preflight for the contacts you want to enroll.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight`
```json theme={null}
{
"filters": {
"leadIds": [
"CONTACT_ID_1",
"CONTACT_ID_2",
"CONTACT_ID_3"
]
},
"limit": 3,
"selectionScope": "all"
}
```
```json theme={null}
{
"filters": {
"validationRunId": "VALIDATION_RUN_ID"
},
"limit": 100,
"selectionScope": "not_in_sequence"
}
```
`selectionScope` controls which matching contacts are included:
* `all` includes all matching contacts.
* `in_sequence` includes contacts with at least one enrollment that could produce a preflight decision: any enrollment in a draft sequence, or an enrollment with status `active`, `paused`, `out_of_office`, `failed`, `company_limit_reached`, or `replied`.
* `not_in_sequence` includes contacts without one of those decision-producing enrollments. A contact whose only enrollments are `completed`, `dnc`, `unsubscribed`, or `bounced` is included in this scope unless one of those enrollments belongs to a draft sequence.
If you omit `selectionScope`, it defaults to `all`.
A `validationRunId` must belong to the workspace, be completed, and contain at least one contact. If you also provide `leadIds`, the API keeps only contacts present in both selections.
The response shows how many contacts can be enrolled and which contacts need a decision:
```json theme={null}
{
"preflightId": "2f4f75ad-2527-4e89-b0dc-2cd75589c64f",
"expiresAt": "2026-08-18T13:15:00Z",
"summary": {
"candidateCount": 4,
"decisionRequiredCount": 2,
"automaticEnrollmentCount": 1,
"alreadyInTargetCount": 1
},
"moveGroups": [
{
"sequenceId": 42,
"sequenceName": "Existing outbound",
"sequenceStatus": "active",
"selectedContactCount": 2
}
],
"repliedDecision": {
"contactCount": 1
}
}
```
| Field | Meaning |
| ------------------------------ | ---------------------------------------------------------------- |
| `candidateCount` | Contacts checked by the preflight. |
| `decisionRequiredCount` | Contacts that are already in another sequence or have replied. |
| `automaticEnrollmentCount` | Contacts that can be enrolled without changing another sequence. |
| `alreadyInTargetCount` | Contacts already enrolled in this sequence. |
| `moveGroups` | Sequences that contain contacts you can move. |
| `repliedDecision.contactCount` | Contacts that have replied in another sequence. |
A contact may appear in more than one move group. For that reason, move-group contact counts may overlap.
Save `preflightId` for the next request. The preflight expires at `expiresAt`, 15 minutes after creation.
Choose the action that matches what you want to do:
* **Skip** enrolls contacts that do not need a decision. It leaves the other contacts in their current sequences.
* **Move** enrolls eligible contacts in this sequence and updates their enrollment in the sequences you select.
To move contacts, select the relevant `sequenceId` values from `moveGroups`. If a contact appears in several required groups, select all of them. Otherwise, the API skips that contact.
When a move is confirmed, a draft source enrollment is deleted. Any other selected source enrollment is set to `completed`, and its pending steps are canceled.
This step is optional. It shows the expected result without changing any enrollments.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight/{preflightID}/move-preview`
```json theme={null}
{
"moveSourceSequenceIds": [42],
"skipReplied": true
}
```
You must set `skipReplied`:
* `true` does not enroll contacts that have replied.
* `false` allows them to be enrolled if you selected all required sequences.
The response shows the expected outcome:
```json theme={null}
{
"summary": {
"enrolledCount": 2,
"skippedCount": 1,
"sourceCleanupCount": 1,
"alreadyInTargetCount": 1
},
"skipBreakdown": {
"repliedCount": 1,
"unselectedSourceCount": 0
}
}
```
`sourceCleanupCount` is the number of contacts whose enrollment in another sequence will be updated. `skipBreakdown` shows whether contacts are skipped because they replied or because you did not select every required sequence.
Confirm the preflight with `skip` or `move`.
`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/preflight/{preflightID}/confirm`
```json theme={null}
{
"action": "skip"
}
```
```json theme={null}
{
"action": "move",
"moveSourceSequenceIds": [42],
"skipReplied": true
}
```
`skipReplied` is required for `move`. It is not required for `skip`.
A successful response contains the enrolled contact IDs and the final counts:
```json theme={null}
{
"enrolledLeadIds": [
"CONTACT_ID_1",
"CONTACT_ID_2"
],
"summary": {
"enrolledCount": 2,
"skippedCount": 1,
"sourceCleanupCount": 1,
"alreadyInTargetCount": 1
}
}
```
Preview returns expected counts. Confirmation returns the counts that were actually applied.
A contact suppressed elsewhere by do-not-contact, unsubscribe, or bounce shield is enrolled with that status and is never contacted. The response still includes the contact in `enrolledLeadIds` and `enrolledCount`, without a field that distinguishes it from a contactable enrollment. Do not treat `enrolledCount` as the number of contactable contacts.
## If the preflight changes or expires
Contact enrollments can change after you create a preflight. If this happens, preview or confirmation returns `409` with `message` set to `preflight_stale`.
The `data` field contains a new preflight:
```json theme={null}
{
"message": "preflight_stale",
"data": {
"preflightId": "9a650383-6a70-44d6-8ba7-af366065cf89",
"expiresAt": "2026-08-18T13:20:00Z",
"summary": {
"candidateCount": 4,
"decisionRequiredCount": 0,
"automaticEnrollmentCount": 3,
"alreadyInTargetCount": 1
},
"moveGroups": [],
"repliedDecision": {
"contactCount": 0
}
}
}
```
Review the new result and use `data.preflightId` for the next preview or confirmation request.
An expired preflight returns `404`. Create another preflight with the same contact selection. A `422` response with `insufficient remaining active credits balance` means confirmation would exceed the account contact limit. A `423` response with `enrollment_confirmation_busy` means another enrollment is being confirmed; try again after it finishes.
## If a validation run is not ready
When you use `validationRunId`, the run must be completed and contain at least one contact.
| Validation run | Error | What to do |
| -------------------------- | ------------------------------------------- | ------------------------------------------------------------ |
| Pending or in progress | `409` with `validation_run_not_completed` | Wait for the run to complete, then create another preflight. |
| Failed | `409` with `validation_run_failed` | Start or select another validation run. |
| Completed with no contacts | `400` with `validation_run_selection_empty` | Change the validation selection or contact filters. |
## Related resources
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Authentication](/authentication)
* [API endpoints](/salesforge-api/api-endpoints)
# Verify webhook signatures
Source: https://developer.salesforge.ai/products/salesforge/verify-webhook-signatures
Create a signed webhook, then verify the HMAC-SHA256 signature on each delivery before you trust it.
A webhook URL is a public, unauthenticated endpoint. Anyone who learns it can send it a forged request. Signed webhooks let your receiver confirm that a delivery actually came from Salesforge and was not altered in transit.
Only webhooks created through the public API are signed today. Webhooks created in the app UI, Zapier, or Make are not signed yet.
## Prerequisites
* `WORKSPACE_ID`
* `SALESFORGE_API_KEY`
* A publicly reachable URL that answers `2xx` to receive deliveries
For the shared authentication pattern, see [Authentication](/authentication).
## How it works
```mermaid theme={null}
flowchart LR
A[Create a webhook] --> B[Salesforge returns a signing secret once]
B --> C[An event occurs]
C --> D[Salesforge signs and sends the delivery]
D --> E[Your endpoint recomputes the signature and compares it]
```
Each webhook gets its own secret, generated once and returned only in the create response. Every delivery is signed with HMAC-SHA256 over the event ID, the send timestamp, and the raw request body.
`POST /workspaces/{workspaceID}/integrations/webhooks`
```json theme={null}
{
"name": "Reply notifications",
"type": "email_replied",
"url": "https://example.com/webhooks/salesforge"
}
```
`type` is the event that triggers this webhook. Omit `sequenceIds` to receive the event from every sequence, or set it to scope the webhook to specific sequences.
The response includes `signingSecret`:
```json theme={null}
{
"id": "wh_config_9f8e7d6c5b4a",
"name": "Reply notifications",
"url": "https://example.com/webhooks/salesforge",
"type": "email_replied",
"sequenceIds": [],
"sentCount": 0,
"signingSecret": "whsec_AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8"
}
```
`signingSecret` is returned exactly once. Store it immediately — it is never included in later responses. If you lose it, delete the webhook and create a new one; there is no rotation or retrieval endpoint.
| Header | Example | Use |
| ---------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------- |
| `X-SalesforgeAI-Webhook-Event-ID` | `wh_0123456789abcdef` | First field of the signed content, and your deduplication key |
| `X-SalesforgeAI-Webhook-Signature` | `t=1754563200,v1=d35d4326...` | `t` is the Unix timestamp that was signed. `v1` is the lowercase hex HMAC-SHA256 digest |
| `User-Agent` | `SalesforgeAI/salesforge-webhook-service-1.1` | Identifies the sender. Never use this to authenticate a request |
Example delivery used throughout this guide:
```json theme={null}
{
"webhookInfo": {
"type": "email_sent"
}
}
```
Treat the body as opaque bytes until the signature check passes — do not parse it first.
1. Split the signature header on `,`, then each part on its first `=`, to read `t` and `v1`.
2. Reject the delivery if `t` is more than 300 seconds from your current time.
3. Rebuild the signed content by joining the event ID, the `t` value exactly as received, and the raw request body with literal `.` characters: `event_id + "." + t + "." + raw_body`.
4. Compute HMAC-SHA256 of that content using the full secret — including the `whsec_` prefix — as the key, and compare the result to `v1` with a constant-time comparison.
Reject any delivery with no signature header. A missing header means the request either did not come from Salesforge, or came from a webhook created through a path that does not sign yet.
```python verify_webhook.py theme={null}
import hashlib
import hmac
import time
def verify_webhook(secret, event_id, header, raw_body, tolerance_seconds=300):
"""raw_body must be the exact, unparsed request body (bytes)."""
if not (secret and event_id and header):
return False
fields = {}
for part in header.split(","):
key, sep, value = part.partition("=")
if sep:
fields[key] = value
timestamp, signature = fields.get("t"), fields.get("v1")
if not timestamp or not signature or not timestamp.isdigit():
return False
if abs(int(time.time()) - int(timestamp)) > tolerance_seconds:
return False
signed_content = f"{event_id}.{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed_content, hashlib.sha256).digest()
try:
received = bytes.fromhex(signature)
except ValueError:
return False
return hmac.compare_digest(expected, received)
```
```javascript verifyWebhook.js theme={null}
const crypto = require('node:crypto');
function verifyWebhook({ secret, eventId, header, rawBody, toleranceSeconds = 300 }) {
if (!secret || !eventId || !header) return false;
const fields = {};
for (const part of String(header).split(',')) {
const i = part.indexOf('=');
if (i > 0) fields[part.slice(0, i)] = part.slice(i + 1);
}
const { t, v1 } = fields;
if (!t || !v1 || !/^\d+$/.test(t)) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSeconds) return false;
const body = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody, 'utf8');
const signedContent = Buffer.concat([Buffer.from(`${eventId}.${t}.`, 'utf8'), body]);
const expected = crypto.createHmac('sha256', secret).update(signedContent).digest();
const received = Buffer.from(v1, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}
```
```go verify_webhook.go theme={null}
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"strconv"
"strings"
"time"
)
const toleranceSeconds = 300
// VerifyWebhook checks a Salesforce webhook delivery. rawBody must be
// the exact, unparsed request body.
func VerifyWebhook(secret, eventID, header string, rawBody []byte) bool {
if secret == "" || eventID == "" || header == "" {
return false
}
fields := map[string]string{}
for _, part := range strings.Split(header, ",") {
key, value, found := strings.Cut(part, "=")
if found {
fields[key] = value
}
}
timestamp, signature := fields["t"], fields["v1"]
if timestamp == "" || signature == "" {
return false
}
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return false
}
if age := time.Now().Unix() - ts; age > toleranceSeconds || age < -toleranceSeconds {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(eventID + "." + timestamp + "."))
mac.Write(rawBody)
expected := mac.Sum(nil)
received, err := hex.DecodeString(signature)
if err != nil {
return false
}
return subtle.ConstantTimeCompare(expected, received) == 1
}
```
```java WebhookVerifier.java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
public final class WebhookVerifier {
private static final int TOLERANCE_SECONDS = 300;
// rawBody must be the exact, unparsed request body.
public static boolean verify(String secret, String eventId, String header, byte[] rawBody) {
if (secret == null || secret.isEmpty() || eventId == null || eventId.isEmpty()
|| header == null || header.isEmpty()) {
return false;
}
Map fields = new HashMap<>();
for (String part : header.split(",")) {
int i = part.indexOf('=');
if (i > 0) {
fields.put(part.substring(0, i), part.substring(i + 1));
}
}
String timestamp = fields.get("t");
String signature = fields.get("v1");
if (timestamp == null || signature == null || !timestamp.chars().allMatch(Character::isDigit)) {
return false;
}
long now = System.currentTimeMillis() / 1000;
if (Math.abs(now - Long.parseLong(timestamp)) > TOLERANCE_SECONDS) {
return false;
}
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((eventId + "." + timestamp + ".").getBytes(StandardCharsets.UTF_8));
byte[] expected = mac.doFinal(rawBody);
byte[] received = hexToBytes(signature);
return received.length == expected.length && MessageDigest.isEqual(expected, received);
} catch (Exception e) {
return false;
}
}
private static byte[] hexToBytes(String hex) {
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) ((Character.digit(hex.charAt(i * 2), 16) << 4)
+ Character.digit(hex.charAt(i * 2 + 1), 16));
}
return bytes;
}
}
```
Always compare signatures with a constant-time function — `hmac.compare_digest`, `crypto.timingSafeEqual`, `subtle.ConstantTimeCompare`, or `MessageDigest.isEqual`. A regular `==` leaks timing information an attacker can use to guess the signature byte by byte.
Read the raw body before any JSON-parsing middleware touches it, or the signature will never match:
| Framework | How to get the raw body |
| ------------- | ---------------------------------------------------------------------- |
| Flask | `request.get_data()` |
| Express | `express.raw({ type: 'application/json' })` on the route |
| Go `net/http` | `io.ReadAll(r.Body)` — already raw, no extra config needed |
| Java Servlet | `request.getInputStream()` — avoid a filter that parses the body first |
Run this known-good vector through your verifier before pointing it at live traffic:
```plaintext theme={null}
secret = whsec_AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8
event_id = wh_0123456789abcdef
header = t=1754563200,v1=d35d432699cc9e59eb73da4396b9a3a96003ba6a9ae259256d270ff3c8f24207
raw_body = {"webhookInfo":{"type":"email_sent"}}
```
With the timestamp check disabled (the vector is old), it must return `true`. Flip one byte of the secret, event ID, or body, and it must return `false`. If a mutation still passes, your verifier is reading something other than what it signs.
## Common mistakes
| Mistake | Symptom | Fix |
| -------------------------------------------- | ------------------------------------------- | -------------------------------------------------------- |
| Framework parsed the body before you read it | Every signature fails | Read raw bytes before any JSON-parsing middleware runs |
| Stripped the `whsec_` prefix from the secret | Every signature fails | Use the secret exactly as returned, including the prefix |
| Reformatted the timestamp before signing | Fails only on some deliveries | Reuse the `t` value from the header as a string |
| Compared signatures with `==` | Passes, but leaks timing information | Use a constant-time comparison |
| Receiver clock drift | Valid deliveries rejected as stale | Keep the receiver's clock synced (NTP) |
| Accepted requests with no signature header | No visible symptom until an event is forged | Reject any delivery missing the signature header |
## Related resources
* [Authentication](/authentication)
* [Security best practices](/authentication/security-best-practices)
* [API endpoints](/salesforge-api/api-endpoints)
# Authentication
Source: https://developer.salesforge.ai/salesforge-api/api-endpoints
Authenticate with Salesforge API and multichannel sequencing endpoints before using the endpoint reference.
Use this page as the authentication entry point before working through the Salesforge API endpoint reference.
Salesforge public API endpoints and multichannel sequencing endpoints use the same Salesforge API key.
Send the raw API key in the `Authorization` header. Do not add a `Bearer ` prefix.
```http theme={null}
Authorization: YOUR_API_KEY
```
For the complete authentication guide, see [Authentication](/authentication).
## How authentication applies here
Use the same raw API key format for both endpoint groups:
* **Salesforge endpoints** require the raw key in the `Authorization` header.
* **Multichannel sequencing endpoints** accept the raw key and also accept an optional `Bearer ` prefix.
## Before using the endpoint reference
1. Generate the correct API key for the workspace or product context you are integrating.
2. Send that raw key in the `Authorization` header on every request.
3. Review [Security best practices](/authentication/security-best-practices) before using the interactive reference or production integrations.
## Related resources
* [Authentication](/authentication)
* [Workspace](/salesforge-api/concepts/workspace)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Leads / Contacts
Source: https://developer.salesforge.ai/salesforge-api/concepts/leads-contacts
Understand how leads and contacts map in Salesforge API workflows.
In Salesforge, leads and contacts represent the same operational entity.
Internally, the domain model is called a lead. In public-facing workflows and API language, the same record may be described as a contact. For documentation purposes, the important concept is that both names refer to the same person or company-level record used in outreach workflows.
## Identity model
A lead or contact is primarily identified through one of two outreach identities:
* **Email**: the email identity used for email-first workflows.
* **LinkedIn identity**: the LinkedIn profile identity used for LinkedIn-first or multichannel workflows.
In practice, a record may be centered on email, LinkedIn, or both. Supporting fields such as first name, last name, and company add context, but email and LinkedIn identity are the primary fields that anchor the record in sequence and conversation workflows.
## Role in outreach workflows
Leads or contacts are the records that sequences enroll, threads attach to, and sender actions target.
They are not just profile records. They are the operational unit that moves through sequencing, receives messages, replies in threads, and accumulates status or workflow outcomes over time.
For a concrete multichannel example, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence), which creates email-only, LinkedIn-only, and mixed-channel lead records before enrollment.
## Practical usage patterns
1. Model leads and contacts as one concept in your integration layer to avoid unnecessary duplication.
2. Treat email and LinkedIn identity as the primary outreach anchors for the record.
3. Assign leads or contacts to sequence workflows only after sender and sequence setup is ready.
## Related resources
* [Threads](/salesforge-api/concepts/threads)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Sequence (V1)](/salesforge-api/concepts/sequence-v1-email-only)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# LinkedIn Account
Source: https://developer.salesforge.ai/salesforge-api/concepts/linkedin-account
Understand LinkedIn account operations in multichannel sequencing.
A LinkedIn account is the LinkedIn-side identity used inside a sender profile.
Within multichannel sequencing, it is the account that powers LinkedIn actions such as connection requests, messages, and other LinkedIn workflow steps.
## Role inside sender profile
The LinkedIn account is the LinkedIn component of sender identity.
When a sender profile is assigned to a sequence, the LinkedIn account under that profile is what makes LinkedIn execution possible. Without a usable LinkedIn account, LinkedIn-specific branches may be unavailable or fail to run as intended.
For an end-to-end example of a sequence that routes eligible leads into a LinkedIn branch after sender-profile setup, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence).
## Connection state and readiness
The multichannel-api public surface exposes a caller-friendly connection state model for LinkedIn accounts.
* **Connected**: the account is ready for use.
* **Awaiting OTP**: the account is waiting for an OTP or checkpoint step before it can be used.
* **Pending**: the connection is still in progress and should not yet be treated as fully ready.
* **Failed**: the account has reached a terminal error state and cannot be relied on for execution.
This higher-level connection state is more useful for documentation than the full internal status set because it reflects the actions an integrator actually needs to take.
## Profile data and execution context
LinkedIn accounts can also carry descriptive profile information such as name, LinkedIn URL, profile image, and premium type.
For documentation purposes, the important concept is not the profile metadata itself, but whether the account is connected and suitable for the sender profile that depends on it.
## Practical usage patterns
1. Connect the LinkedIn account before relying on it in a sender profile used by active sequences.
2. Resolve OTP or checkpoint challenges before treating the account as ready.
3. Re-check connection state before launch or before investigating LinkedIn execution issues.
## Related resources
* [Sender Profile](/salesforge-api/concepts/sender-profile)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Mailboxes
Source: https://developer.salesforge.ai/salesforge-api/concepts/mailboxes
Understand mailbox operations for sending and replies in Salesforge API.
Mailboxes are the email-side identities used inside a sender profile.
Within multichannel execution, mailboxes provide the actual email sending and reply-handling context for sequence actions.
## Role inside sender profile
A sender profile can hold one or more mailboxes.
That lets the sender profile represent a complete sender context for email execution, while the sequence decides when email actions should occur.
For an end-to-end example of how mailbox-backed email execution is enabled through sender-profile assignment in a multichannel flow, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence).
In the multichannel-api public response model, mailboxes are exposed under sender profiles as mailbox identities with values such as ID and address. In salesforge-api, mailbox health and status are operationally richer, which is why mailbox readiness matters even when this concept page stays abstract.
## Mailbox readiness and health
At the concept level, a mailbox should be thought of as either usable for sending, still being prepared, or unavailable for reliable execution.
Across Salesforge mailbox handling, operational states such as **Active**, **Pending**, **Access Lost**, and **Suspended** are important because they affect whether email steps can proceed safely.
* **Active** generally represents a mailbox that is ready for normal sending behavior.
* **Pending** represents a mailbox that exists but is not yet fully ready.
* **Access Lost** means the mailbox connection or authorization is no longer healthy.
* **Suspended** means the mailbox should not be used for normal sending until the issue is resolved.
For shared product-level guidance, see [Mailbox concept](/overview/mailbox).
## Practical usage patterns
1. Keep mailbox assignment aligned with the sender profile that will execute the sequence.
2. Validate mailbox readiness before launch and when investigating delivery issues.
3. Use mailbox identity consistently for reply handling and thread continuity.
## Related resources
* [Threads](/salesforge-api/concepts/threads)
* [Sender Profile](/salesforge-api/concepts/sender-profile)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Multichannel Sequence (V2)
Source: https://developer.salesforge.ai/salesforge-api/concepts/multichannel-sequence
Understand how Multichannel Sequence (V2) models outreach across email and LinkedIn with condition-based paths and configurable behavior.
Multichannel outreach means coordinating communication across more than one channel in a single journey. In Sequencing V2, the currently supported channels are email and LinkedIn, so you can combine both in one workflow instead of running separate campaigns.
Compared with V1 (which is mostly linear and email-first), V2 supports more custom outreach flows. You can adapt each contact path based on outcomes, choose different channel actions at different points, and build workflows that better match real engagement behavior.
## DAG abstraction
A DAG (directed acyclic graph) is a directed graph where nodes are connected by one-way edges and no path can return to an earlier node (no cycles). In V2, this abstracts outreach into reusable workflow building blocks instead of a fixed linear sequence.
At runtime, each contact moves forward through one path in that graph based on condition outcomes.
* Root node: the single start point of every multichannel sequence graph.
* Nodes: executable steps or evaluations in the sequence graph.
* Condition nodes: decision points (for example, whether a contact replied within a wait period) that choose the next path.
* Branches: routing paths between nodes.
* Actions (Action nodes): channel operations such as send email, connect on LinkedIn, send LinkedIn message, or wait.
In execution and tree views, a path can also end when a branch has no next node (terminal end state).
When you create a multichannel sequence, Salesforge automatically creates the Root node for you.
If you want to see that creation flow through the public APIs, including action and condition discovery, sender-profile assignment, optional validation, enrollment, and launch, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence).
```mermaid theme={null}
graph TD
A[Root node: Start] --> B[Action node: Send email]
B --> C{Condition node: Replied within wait period?}
C -- No --> E[Action node: LinkedIn connect]
E --> F{Condition node: Connected?}
F -- Yes --> G[Action node: LinkedIn message]
F -- No --> H[Action node: Follow-up email]
G --> I[Continue sequence path]
H --> I
```
In this example, the **Yes** path from the replied condition is not connected to another node. It can terminate by leaving `to_node_id` empty on that branch.
## Workflow building blocks
V2 workflows are assembled from catalogs of available actions and conditions.
* Actions define what the system does next (for example email send, LinkedIn connect, LinkedIn message, or wait).
* Condition nodes define how the workflow decides the next path after a measurable outcome.
* Branches connect those blocks into contact journeys.
This model lets you create simple or advanced flows without changing the core sequence concept.
## Sequence settings
Sequence settings define how the sequence behaves once the workflow structure is in place. On the public multichannel sequencing surface, the exposed settings are focused on email presentation, sender behavior, opt-out presentation, and opportunity attribution.
The underlying multichannel-api service has a broader internal settings model, but the public configuration surface for sequence creation and update is centered on the settings below.
### Tracking and message presentation
* **Open Tracking**: tracks email opens for the sequence.
* **Plain-Text Emails**: uses a plain-text style email presentation for the sequence.
Example: a team that wants a simpler email format may enable **Plain-Text Emails**, while a team measuring top-of-funnel engagement may enable **Open Tracking**.
### Sender behavior and recipient copy rules
* **ESP Matching**: applies provider-matching behavior for outbound email delivery.
* **CC and BCC**: when enabled, the sequence can include CC and BCC recipients.
* **CC Recipients**: when enabled, defines the CC recipient list.
* **BCC Recipients**: when enabled, defines the BCC recipient list.
Example: a team might use **BCC Recipients** to archive outbound mail in a shared mailbox, or enable **ESP Matching** when sender-routing policy needs to stay aligned with mailbox provider behavior.
### Compliance and opt-out behavior
* **Opt-out Text**: when enabled, defines the wording shown for the opt-out text.
* **Opt-out Link**: when enabled, adds an opt-out link to the message.
* **Opt-out Link Text**: when enabled, defines the label shown for the opt-out link.
Example: a sequence can show a short custom sentence such as "If this is not relevant, feel free to opt out" and pair it with a clearer link label such as "Unsubscribe from this sequence".
### Opportunity tracking behavior
* **Opportunity Tracking**: applies opportunity-oriented attribution to the sequence.
* **Opportunity Value**: when enabled, assigns a value used for opportunity tracking.
Example: if a team wants sequence outcomes to carry a nominal business value, it can enable **Opportunity Tracking** and assign an **Opportunity Value** for reporting consistency.
### Update model
V2 settings updates are partial: you can update only the fields you want to change while leaving other settings as they are.
In practice, this means you can adjust one part of sequence behavior, such as opt-out text or CC/BCC handling, without resubmitting the full settings object.
## Operational context
### Scheduling and timezone
Each sequence runs within schedule windows and a sequence timezone. This controls when sends are allowed, independent of workflow logic.
### Sender profiles in multichannel execution
Sender profiles are required execution identities for multichannel workflows. They align mailbox and LinkedIn sender context so channel actions can run consistently.
See [Sender Profile](/salesforge-api/concepts/sender-profile) for the concept overview.
### Enrollment and validation flow
Contacts are enrolled into the sequence, and validation can be used as a readiness step before active outreach. This helps maintain quality before contacts enter live execution.
For a concrete public API walkthrough of optional validation and multiple enrollment patterns, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence).
### Sequence lifecycle
V2 sequences use a small lifecycle model that describes whether the sequence is being prepared, running, temporarily stopped, or finished.
* **Draft**: the sequence is still being configured. This is the planning state for workflow structure, schedules, sender assignment, and settings.
* **Active**: the sequence is live and can continue processing enrolled contacts according to its workflow, schedule, and sender configuration.
* **Paused**: the sequence remains defined, but execution is temporarily stopped until it is resumed.
* **Completed**: the sequence lifecycle has been finished and is no longer running as an active sequence.
### Contact lifecycle outcomes
Each enrolled contact also has its own lifecycle status. This is more granular than the sequence lifecycle because contacts can finish for different reasons even while the overall sequence remains active.
In the current model, **Active** and **Paused** are in-progress states. The remaining statuses are treated as terminal outcomes.
* **Active**: the contact is currently enrolled and can continue moving through the workflow.
* **Paused**: the contact is temporarily stopped and is not progressing until reactivated.
* **Completed**: the contact reached the end of its intended path without another terminating outcome taking precedence.
* **Failed**: the contact could not continue because of an execution or processing failure.
* **Replied**: the contact replied, so the sequence stops further automated progression for that contact.
* **Out of Office**: an out-of-office response was detected and the contact is moved into that outcome state.
* **Unsubscribed**: the contact opted out and should no longer receive sequence outreach.
* **DNC**: the contact is marked do-not-contact and is not eligible for further outreach in the sequence.
* **Bounced**: an email delivery bounced, so the contact is moved into a delivery-failure outcome.
* **Bounce Shield**: the contact was stopped by bounce-protection logic intended to reduce delivery risk.
## Practical implementation path
1. Define the outreach goal and the main positive/negative outcomes.
2. Create the sequence, configure timezone and schedule windows.
3. Add action nodes and condition nodes.
4. Connect branch paths for positive and fallback outcomes.
5. Assign sender profiles and enroll contacts.
6. Launch and monitor outcomes by lifecycle state.
## Related resources
* [Sender Profile](/salesforge-api/concepts/sender-profile)
* [LinkedIn Account](/salesforge-api/concepts/linkedin-account)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Primebox Labels
Source: https://developer.salesforge.ai/salesforge-api/concepts/primebox-labels
Understand Primebox labels for conversation classification in Salesforge API.
Primebox labels are workspace-scoped categories used to classify conversation outcomes.
They are attached to threads and help normalize how reply workflows are organized, interpreted, and routed.
## What labels represent
At the concept level, a Primebox Label describes the current interpretation or outcome of a conversation.
That interpretation might be positive, negative, complete, out of office, meeting-related, or simply not relevant to the intended recipient. The key idea is that labels turn raw conversation activity into a reusable workflow signal.
## Built-in and workspace meaning
Salesforge supports built-in label behavior for common outcomes such as:
* **Positive**
* **Negative**
* **Out of Office**
* **Meeting Booked**
* **Meeting Completed**
* **Closed**
* **Wrong Contact**
These built-in labels provide a shared classification vocabulary for conversation handling. Within a workspace, labels become the way teams standardize how they interpret and process thread outcomes.
## Relationship to threads
Primebox Labels belong conceptually to threads.
They are not just free-form tags. A label changes how a conversation is understood inside inbox workflows, reporting, automation, and follow-up decision-making.
Because labels are attached to threads, they work as conversation-level signals rather than mailbox-level or contact-level attributes.
## Workflow relevance
Label changes can influence downstream behavior.
Depending on the workflow design, a label can affect triage, reporting, webhook events, or subsequence handling. This is why label assignment is best treated as part of conversation state management rather than as cosmetic inbox organization.
## Practical usage patterns
1. Map labels to meaningful business outcomes in your integration model.
2. Apply labels when automation or human review determines the current conversation outcome.
3. Use thread labels as routing and reporting signals rather than as display-only metadata.
## Related resources
* [Threads](/salesforge-api/concepts/threads)
* [Leads / Contacts](/salesforge-api/concepts/leads-contacts)
# Sender Profile
Source: https://developer.salesforge.ai/salesforge-api/concepts/sender-profile
Understand sender profile composition and assignment in multichannel sequences.
A sender profile is the execution identity used by a multichannel sequence.
Conceptually, it brings related channel identities together so the sequence can act through a consistent sender context across email and LinkedIn.
In practice, a sender profile is centered on three things:
* a sender profile name,
* a LinkedIn account used for LinkedIn actions,
* one or more mailboxes used for email actions.
This is why LinkedIn Account and Mailboxes are best understood as parts of the sender profile model rather than as completely separate execution concepts.
## What the sender profile controls
A sender profile determines which sender identities a sequence can use at runtime.
* For email actions, it provides the mailbox pool available for sending.
* For LinkedIn actions, it provides the LinkedIn identity available for connection requests, messages, and related LinkedIn steps.
* For multichannel execution, it keeps those identities aligned under one sender-level concept.
This separation helps sequence design stay focused on workflow logic, while sender profile configuration controls who the sequence is acting as.
## Status and readiness
In the current model, sender profiles use a small status model:
* **Draft**: the sender profile exists but should still be treated as being prepared.
* **Active**: the sender profile is ready to be used in sequence assignment and execution.
Operationally, sender profile readiness depends on the readiness of the linked channel identities under it. If the LinkedIn account is not usable or the assigned mailboxes are not usable, execution quality is affected even if the sender profile object itself exists.
## Sequence assignment
Sender profiles are assigned to sequences, not just to individual steps.
That lets the workflow decide when to use email or LinkedIn, while the sender profile decides which underlying identities are available when that action runs.
For the public API sequence-assignment flow, including how a sender profile is applied before launch, see [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence).
## Practical usage patterns
1. Configure sender profiles before launch so sender identity is stable when execution begins.
2. Keep LinkedIn and mailbox configuration aligned under the same sender context.
3. Review sender profile readiness before assigning it to active sequences.
## Related resources
* [LinkedIn Account](/salesforge-api/concepts/linkedin-account)
* [Mailboxes](/salesforge-api/concepts/mailboxes)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Sequence (V1)
Source: https://developer.salesforge.ai/salesforge-api/concepts/sequence-v1-email-only
Understand legacy V1 sequence behavior, data model, and lifecycle in Salesforge public API.
Sequence V1 is deprecated for new builds. For new orchestration work, prefer [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence).
V1 is the legacy Salesforge sequencing engine and is primarily email-oriented.
Use V1 when you need compatibility with existing V1 workflows and payloads.
## Steps
In V1, a sequence is a series of emails sent over time.
Each step is usually one email in that series.
Example of steps:
Step 1: Usually an initial email. A sequence must have at least one step.
Subject: `Quick intro for {{company}}`
Body:
```text theme={null}
Hi {{first_name}} {{last_name}},
I noticed your team at {{company}} is scaling outbound.
Would it help if I share a short playbook we use to improve reply rates?
Best,
Alex
```
Step 2: Next steps after Step 1 are usually follow-up emails.
Subject: `Following up on my previous note`
Body:
```text theme={null}
Hi {{first_name}} {{last_name}},
Just following up in case my last email got buried.
If this is relevant, I can send 2-3 ideas tailored for {{company}} around {{priority_goal}}.
Best,
Alex
```
### Step variants
A step variant is an alternative version of the same email step.
Variants let you run message experiments while keeping the same timing position in the sequence.
Good variant design keeps intent consistent and changes only the messaging hypothesis.
Example conceptual variants for one step:
* Variant A: direct, outcome-first framing.
* Variant B: problem-first framing with a softer CTA.
### Custom variables in step content
V1 supports variable-based personalization in step content.
Variables inject recipient or account context at send time, which helps keep messages relevant without writing one-off copy per contact.
Use built-in variables such as `{{first_name}}`, `{{last_name}}`, and `{{company}}` for core personalization, and add custom variables for business-specific context.
Custom variable syntax in message content uses `{{var_name}}`.
Example placeholders in copy:
* Subject: `Quick question for {{company}}`
* Body: `Hi {{first_name}} {{last_name}}, I noticed {{company}} is focused on {{priority_goal}}.`
Custom variables used in a message must exist for the enrolled lead/contact. If a referenced variable is missing, the email send will fail.
## Schedules
Schedules define when a sequence is allowed to send. Conceptually, schedules are delivery windows that gates execution.
In practice, teams align schedule windows with recipient time zones, expected reply behavior, and deliverability goals.
## Status model
V1 uses a two-level status model: sequence lifecycle and contact lifecycle.
### Sequence lifecycle
A sequence moves through planning, active execution, pause, and completion-oriented phases.
Example lifecycle path: `draft` -> `active` -> `paused` -> `active` -> `completed`.
### Contact lifecycle
Each contact also moves through a lifecycle model that captures progress, pauses, failures, replies, and completion.
Common contact statuses include `active`, `paused`, `failed`, `replied`, and `finished`, plus outcome-specific statuses such as `bounced` and `unsubscribed`.
Example: one contact can finish normally while another is paused or moved into retry handling.
## Practical usage patterns
1. Design sequence intent first, then map steps and variants to that intent.
2. Keep variant differences deliberate and measurable.
3. Use variables for personalization and always provide fallback wording.
4. Operate with lifecycle-state discipline for monitoring.
5. **Use V2 for net-new multichannel orchestration work.**
## Minimum requirements for a sequence
Before a V1 sequence can run, it must include all of the following:
* At least one step with at least one variant.
* At least one enrolled lead/contact.
* At least one mailbox assigned for sending.
* At least one valid schedule.
Example readiness checklist:
1. Create Step 1 with one variant.
2. Enroll at least one lead/contact.
3. Assign at least one active mailbox.
4. Add at least one schedule window.
5. Activate the sequence.
## Related resources
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Create a multichannel sequence](/products/salesforge/create-multichannel-sequence)
# Threads
Source: https://developer.salesforge.ai/salesforge-api/concepts/threads
Understand thread lifecycle and reply handling in Salesforge API.
A thread is the conversation record associated with a mailbox and a lead or contact.
It acts as the container for conversation state, message history, reply handling, labels, and follow-up workflow context.
## Conversation scope
At the concept level, a thread brings together three things:
* the sender side of the conversation, usually through a mailbox, or LinkedIn account,
* the lead or contact on the receiving side,
* the conversation history that belongs to that relationship.
This is why threads are central to reply workflows. They represent the conversation as a whole, not just one message.
## Message history and channel context
Threads are used to read reply history and conversation context over time. Thread context can carry multichannel relevance, including LinkedIn message or Email context in the broader conversation model. For documentation purposes, the important concept is that a thread is the place where conversation continuity is understood.
## Labels and triage
Threads are the objects that Primebox Labels classify. Once a thread is labeled, that label can be used for inbox organization, routing, reporting, subsequence or integrations behavior, or human review.
## Practical usage patterns
1. Use threads as the main source of truth for conversation state.
2. Apply labels at the thread level when triage or workflow routing depends on conversation outcome.
3. Review thread-level signals when deciding whether automation should continue or hand off to a human.
## Related resources
* [Primebox Labels](/salesforge-api/concepts/primebox-labels)
* [Mailboxes](/salesforge-api/concepts/mailboxes)
* [Leads / Contacts](/salesforge-api/concepts/leads-contacts)
# Workspace
Source: https://developer.salesforge.ai/salesforge-api/concepts/workspace
Understand workspace scope in Salesforge public API integrations.
A workspace is the primary scope boundary in Salesforge API. Most resources are scoped by workspace ID.
For the shared product-level definition, see [Workspace concept](/overview/workspace).
## Practical usage patterns
1. Resolve workspace first, then perform resource operations inside that scope.
2. Persist workspace IDs in your integration state.
3. Treat workspace authorization failures as scope errors, not transport failures.
## Related resources
* [Leads / Contacts](/salesforge-api/concepts/leads-contacts)
* [Sender Profile](/salesforge-api/concepts/sender-profile)