Salesforce Marketing Cloud

Trigger SFMC Journeys from real-time MailOdds intent

Use MailOdds intent.hot_lead webhooks to insert rows into a Marketing Cloud Data Extension, then trigger a Journey Builder flow with AMPscript-personalised email and an optional MobileConnect SMS step.

Setup time: 90 min
Difficulty: Advanced
1,000 free validations included

Prerequisites

  • MailOdds account (free tier works)
  • API key from dashboard

Why webhook + Data Extension instead of Journey Builder Trigger Event

SFMC's proprietary Journey Builder Trigger Event API requires a Marketing Cloud package install per source system. For most teams it is faster to point the MailOdds webhook at a small middleware (Cloud Function, AWS Lambda, Heroku dyno) that authenticates against the Marketing Cloud REST API and inserts a row into a Data Extension. Journey Builder polls the Data Extension every 5 minutes, picks up new rows, and starts the configured journey.

That trade-off costs you 0-5 minutes of latency vs. the proprietary trigger, and saves you a multi-week package-install conversation with your Salesforce Admin.

MailOdds payload your middleware receives

Same payload as every MailOdds intent webhook. Verify the X-Signature-SHA256 header (HMAC-SHA256 of the raw body, keyed with your webhook secret) before forwarding into Marketing Cloud.

POST your-middleware-url

JSON
{
  "event": "intent.hot_lead",
  "request_id": "evt_b7f3e8a1c9d24f06",
  "timestamp": 1714670400,
  "data": {
    "base_id": "msg_a3f8c2",
    "heat_score": 0.87,
    "dwell_seconds": 142,
    "storefront_context": {
      "last_product_viewed": {
        "id": "sku-1042",
        "title": "Alpaca Runner",
        "price": "129.00",
        "currency": "EUR"
      },
      "cart_value": "129.00",
      "page_type": "product",
      "utm_source": "klaviyo"
    },
    "sms_eligible": true,
    "cross_device": false,
    "device_count": 1,
    "device_types": ["desktop"],
    "most_active_device": "desktop",
    "first_device_seen_at": "2026-04-29T10:14:02Z",
    "cluster_confidence": 1.0
  }
}

REST API call your middleware makes

Two HTTP calls: get an OAuth token, then async-insert one row into the MAILODDS_HOT_LEADS Data Extension. Cache the token until 1 minute before expires_in; tokens are valid for 18 minutes.

forward-to-sfmc.sh

BASH
# Step 1: get a Marketing Cloud OAuth token (Server-to-Server, JWT bearer
# is preferred for production; client_credentials shown here for clarity).

curl -sf -X POST "https://YOUR-SUBDOMAIN.auth.marketingcloudapis.com/v2/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "<MC_CLIENT_ID>",
    "client_secret": "<MC_CLIENT_SECRET>",
    "scope": "data_extensions_write events_write"
  }'

# Returns: { "access_token": "<TOKEN>", "expires_in": 1080, "rest_instance_url": "..." }

# Step 2: insert the MailOdds event into a Data Extension.
# The Data Extension acts as the queue Journey Builder polls.

curl -sf -X POST "${REST_INSTANCE_URL}data/v1/async/dataextensions/key:MAILODDS_HOT_LEADS/rows" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [{
      "request_id": "evt_b7f3e8a1c9d24f06",
      "subscriber_email": "visitor@example.com",
      "heat_score": 0.87,
      "product_id": "sku-1042",
      "product_title": "Alpaca Runner",
      "product_price": "129.00",
      "currency": "EUR",
      "cart_value": "129.00",
      "page_type": "product",
      "utm_source": "klaviyo",
      "cross_device": false,
      "device_count": 1,
      "most_active_device": "desktop",
      "captured_at": "2026-05-02T18:00:00Z"
    }]
  }'

AMPscript inside the Journey email

Pull the MailOdds fields out of the Journey's entry data and personalise the email body. The RedirectTo() wrapper preserves SFMC click tracking on the deep link back to the product page.

hot-lead-email.html

HTML
%%[
  /* AMPscript inside the Journey email step. Pull the MailOdds context out
     of the Journey's Entry Data and personalise the message body. */

  VAR @productTitle, @productPrice, @currency, @heatScore, @cta

  SET @productTitle = AttributeValue("product_title")
  SET @productPrice = AttributeValue("product_price")
  SET @currency = AttributeValue("currency")
  SET @heatScore = AttributeValue("heat_score")

  IF @heatScore >= 0.9 THEN
    SET @cta = "Reserve yours - free EU shipping"
  ELSE
    SET @cta = "Take another look"
  ENDIF
]%%

<h1>Still thinking about %%=v(@productTitle)=%%?</h1>
<p>It's still in stock at %%=v(@currency)=%% %%=v(@productPrice)=%%.</p>
<a href="%%=RedirectTo(Concat('https://shop.alpacanica.com/p/', AttributeValue('product_id'), '?utm_source=sfmc&utm_campaign=hot_lead'))=%%">
  %%=v(@cta)=%%
</a>

Journey Builder configuration

Build this in Marketing Cloud under Journey Builder -> Journeys -> New Journey -> Multi-Step Journey. The checklist below maps each MailOdds field to a Journey activity.

journey-builder-checklist.txt

BASH
Journey Builder UI checklist
============================
1.  Journeys -> New Journey -> Multi-Step Journey

2.  Entry Source: Data Extension
    -> Choose Data Extension MAILODDS_HOT_LEADS (created via SOAP or REST,
       primary key: request_id, dedup on insert)
    -> Evaluate every: 5 minutes
    -> Subscriber Source: subscriber_email column, link to Sendable
       Subscriber List

3.  Entry Filter
    -> heat_score >= 0.8
    -> cart_value > 0
    -> product_id IS NOT NULL

4.  Activity 1: Wait 15 minutes
    -> Avoids interrupting the live session

5.  Activity 2: Email
    -> Pick the email containing the AMPscript snippet above
    -> Subject line: %%product_title%% is still in your cart

6.  Activity 3: Decision Split
    -> sms_eligible == true   -> SMS path
    -> sms_eligible == false  -> End

7.  Activity 4 (SMS path): Wait 60 minutes, then MobileConnect SMS step
    -> Body: "Still thinking about %%product_title%%? Free shipping today."

8.  Activity 5: Goal
    -> Definition: contact has fired the "checkout_completed" tracking
       event from MailOdds. Wire this with a second webhook listener that
       inserts into a Goal Data Extension.

9.  Activate the Journey. Test inserts: drop a row into MAILODDS_HOT_LEADS
    via the REST call shown above; the contact should land in Activity 1
    within 5 minutes.

Operational notes

  • Dedup: set the Data Extension primary key to request_id. MailOdds includes a unique request_id per webhook delivery so REST inserts are idempotent.
  • Auth caching: cache the OAuth token in memory or in a small Redis key. SFMC issues a hard quota of 50 token requests per minute per package.
  • Journey polling: 5-minute polling is the SFMC default. Journey Builder will not pick up rows faster than that, no matter how often MailOdds fires the webhook.
  • SMS via MobileConnect: requires a separate provisioning flow with Salesforce. The Decision Split shown above gracefully ends the Journey if MobileConnect is not provisioned.
  • Package types: use a Server-to-Server (S2S) installed package, not a Web App package. The webhook listener never represents a user.