Someone fills out a sweepstakes form. A confirmation email should arrive in seconds, and a short series should follow: a bonus-entry reminder, a winner announcement, maybe an offer for people who opted in. It is about the simplest journey there is, and it is a good way to see how differently Salesforce Marketing Cloud and Braze think. This article builds the same launch on both platforms, step by step, with no CDP in the middle: your form, your backend, and the marketing platform. The difference that matters is not the UI. In SFMC the entry becomes a row in a data extension. In Braze it becomes an event on a person. That difference is the thread I will pull on in a follow-up about events versus data extensions, and it connects to Object Oriented Marketing and Marketing Eventing.
1. The scenario
A holiday sweepstakes with a landing page form. Four fields, two checkboxes, and one rule that trips teams up: entering cannot depend on opting in to marketing. The confirmation is transactional (the person asked for it); everything after it that promotes something is marketing and needs consent.
| Field | Example | Notes |
|---|---|---|
| first_name | Ana | Personalization only |
| ana@example.com | Required to enter and to notify a winner | |
| zip | 94110 | Eligibility (many sweepstakes are US-only or exclude some states) |
| marketing_opt_in | true / false | Optional, never pre-checked, stored with a timestamp |
| rules_accepted | true | Required; links to the Official Rules |
The form posts to your backend, never straight to the marketing platform. The backend validates and rate-limits the entry (sweepstakes attract bots), dedupes it, writes it to your own database, generates a stable entrant ID if the person has no account, and then hands a copy to SFMC or Braze. The platform is a consumer of the entry, not its system of record.
2. Salesforce Marketing Cloud: a journey fed by a data extension
The modern SFMC pattern for “something happened, start a journey” is a Journey Builder API Event entry source. Your backend calls one endpoint and the contact enters the journey. Under the hood, that event is an insert: Journey Builder writes the payload into a data extension and the journey reads from it. So the table comes first.
Step 1. Create the entry data extension
| Field | Type | Length | Key | Required | Notes |
|---|---|---|---|---|---|
| EntryId | Text | 50 | primary key | required | One row per entry, so people can enter more than once |
| ContactKey | Text | 254 | required | Your stable entrant ID, not the email | |
| EmailAddress | EmailAddress | required | Where the email goes | ||
| FirstName | Text | 100 | optional | Personalization | |
| Zip | Text | 10 | optional | Eligibility checks | |
| SweepstakesId | Text | 50 | required | Which sweepstakes, e.g. holiday_2026 | |
| OptIn | Boolean | required | Marketing consent from the checkbox | ||
| EnteredAt | Date | required | Default value: current date |
Every field the form will ever send must exist, with a type and a length, before the first entry. The primary key is EntryId, not ContactKey, so a person can enter more than once. ContactKey should be your stable entrant ID, not the email address: contact keys are permanent, emails are not.
Step 2. Create an installed package and get a token
An admin creates a server-to-server API integration under Setup, Apps, Installed Packages, with Journeys (read, execute) and Data Extensions (read, write) scopes. Your backend trades the client ID and secret for a short-lived token and caches it:
POST https://{subdomain}.auth.marketingcloudapis.com/v2/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "<installed package client id>",
"client_secret": "<secret from your vault>",
"account_id": "<business unit MID>"
}
Step 3. Build the journey on an API Event entry source
In Journey Builder, add an API Event entry source, bind it to Sweepstakes_Entries, and copy the generated EventDefinitionKey. Build the flow: confirmation email, wait three days, a decision split on OptIn, then the bonus-entry email. Set re-entry (no re-entry for one journey per person, or re-entry after exit for daily entries) and activate it.
Step 4. Fire the event from your backend
POST https://{subdomain}.rest.marketingcloudapis.com/interaction/v1/events
Authorization: Bearer <access_token>
Content-Type: application/json
{
"ContactKey": "c_20931",
"EventDefinitionKey": "APIEvent-7c1e2f",
"Data": {
"EntryId": "ent_55120",
"ContactKey": "c_20931",
"EmailAddress": "ana@example.com",
"FirstName": "Ana",
"Zip": "94110",
"SweepstakesId": "holiday_2026",
"OptIn": true
}
}
-> 201 { "eventInstanceId": "8f6e..." }
Every key inside Data must match a field in the data extension. An unknown field, a value longer than its column or a missing required field fails the call. A valid call against a paused or unpublished journey version still writes the row, and nobody enters, which is the bug teams find the morning after launch.
Step 5. Personalize from the row
Hi {{Event.APIEvent-7c1e2f."FirstName"}},
%%[ SET @sweeps = AttributeValue("SweepstakesId") ]%%
You're entered in the %%=ProperCase(Replace(@sweeps, "_", " "))=%% sweepstakes.
%%[ IF AttributeValue("OptIn") == "True" THEN ]%%
Come back tomorrow for a bonus entry.
%%[ ENDIF ]%%
Emails read the row through Journey Data ({{Event.APIEvent-...}}) or AMPscript. Give the confirmation a transactional send classification; the follow-ups need a commercial one and must respect the subscriber's status.
3. Braze: a Canvas triggered by an event
Braze has no table to create. Your backend tells Braze two things in one request: what is true about the person (attributes) and what just happened (an event). A Canvas listens for the event.
Step 1. Create a REST API key
Under Settings, APIs and Identifiers, create a key with the users.track permission and store it in your vault. There is no token exchange; the key goes in the Authorization header.
Step 2. Build the Canvas on an action-based entry
Set the entry schedule to action-based, with the trigger “performs custom event sweepstakes_entered”, optionally filtered on the property sweepstakes_id = holiday_2026. Add the confirmation message, a three-day delay, an audience path on email subscription state, and the bonus-entry message. Set re-eligibility (never, or after 24 hours for daily entries) and an exit criterion. Launch it.
Step 3. Send the entry from your backend
POST https://rest.iad-01.braze.com/users/track
Authorization: Bearer <REST API key>
Content-Type: application/json
{
"attributes": [{
"external_id": "c_20931",
"email": "ana@example.com",
"first_name": "Ana",
"home_zip": "94110",
"email_subscribe": "opted_in"
}],
"events": [{
"external_id": "c_20931",
"name": "sweepstakes_entered",
"time": "2026-11-14T16:05:00Z",
"properties": {
"sweepstakes_id": "holiday_2026",
"entry_id": "ent_55120",
"opt_in": true,
"source": "instagram_ad"
}
}]
}
-> 201 { "attributes_processed": 1, "events_processed": 1, "message": "success" }
Attributes and event names that do not exist yet are created on first use. Only set email_subscribe to opted_in when the box was checked; otherwise leave it alone so you do not overwrite an existing preference. If entrants have no account and you would rather not mint IDs, Braze can also identify users by email or by a user alias in the same endpoint.
Step 4. Personalize from the event
Hi {{${first_name} | default: 'there'}},
You're entered in the {{canvas_entry_properties.${sweepstakes_id} | replace: '_', ' ' | capitalize}} sweepstakes.
{% if canvas_entry_properties.${opt_in} == true %}
Come back tomorrow for a bonus entry.
{% endif %}
canvas_entry_properties exposes the properties of the event that started the Canvas, for every step of it. The rest comes from the profile.
4. Side by side
| SFMC | Braze | |
|---|---|---|
| What the backend calls | /v2/token, then /interaction/v1/events | /users/track |
| Where the entry lands | A row in Sweepstakes_Entries | An event on the user profile |
| Schema | Designed up front: fields, types, lengths, key | Created on first use; manage with blocklists and naming rules |
| Trigger | API Event entry source bound to one data extension | Action-based entry on a custom event |
| Personalization | Journey Data or AMPscript reading the row | Liquid reading the profile and canvas_entry_properties |
| Repeat entries | Re-entry setting plus a primary key that allows duplicates | Re-eligibility setting; every entry is another event |
| Consent | Subscriber status, publication lists, send classification | Subscription state and subscription groups on the profile |
| Reuse later | SQL query activity joining tables on a schedule | Segment filter on the event and its properties |
| Typical failure | Journey not active: row written, nobody enters | Event name typo: event recorded, Canvas never fires |
Neither path is hard once it is set up. The difference is what you have built when you are done. In SFMC you have a table that exists because this campaign exists. In Braze you have a fact about a person that any future campaign can use.
5. After the launch: using the entry again
A week later, someone asks a normal question: which entrants bought something after entering? This is where the two models really diverge.
SFMC: join tables on a schedule
-- Automation Studio, SQL Query activity, nightly -- Target: Sweepstakes_Entrants_Who_Bought (overwrite) SELECT e.ContactKey, e.EmailAddress, COUNT(e.EntryId) AS Entries FROM Sweepstakes_Entries e JOIN Orders_Nightly_DE o ON o.ContactKey = e.ContactKey WHERE e.SweepstakesId = 'holiday_2026' AND o.OrderDate >= e.EnteredAt GROUP BY e.ContactKey, e.EmailAddress
Plus a target data extension, an automation to run it nightly, and the knowledge that the answer is as fresh as last night's run. Next season's sweepstakes gets a new data extension, and this query gets copied and edited.
Braze: a segment
Segment: Holiday entrants who bought
Custom event sweepstakes_entered
property sweepstakes_id = holiday_2026
performed at least 1 time in the last 30 days
AND
Purchased any product in the last 30 days
No new table, no schedule, and it updates as events arrive. Next season's sweepstakes sends the same event with a different sweepstakes_id.
6. Gotchas on each side
SFMC
- Use a stable ID as ContactKey, not the email address. You cannot change a contact key later, and All Contacts is what you are billed on.
- Activate the right version. Editing an active journey creates a new draft version; events go to whatever version is active.
- Match the schema exactly. Send the exact field names, respect the lengths, and use consistent boolean and date formats.
- Set retention on the entry data extension, or it quietly becomes a permanent copy of every entrant's PII.
- Cache the token. Requesting a new one per entry is slow and can hit limits during a launch spike.
Braze
- Name events like facts (
sweepstakes_entered), keep one name across campaigns, and put the campaign in a property. - Watch data points. Attribute updates and custom events count toward your contract; do not resend unchanged attributes on every entry.
- Batch during spikes.
/users/trackaccepts up to 75 events, attributes and purchases each per request. - Do not overwrite consent. Only send
email_subscribewhen the person actually opted in. - Prefer the action-based entry over the API-triggered Canvas endpoint. The event can start this Canvas and every other use of the entry; an API trigger only starts this one.
7. What this sets up
Strip away the UI and the two launches differ in one way. SFMC asks you to design a table, write a row into it and point a journey at the table. Braze asks you to state a fact about a person and let anything that cares about that fact react to it.
Data extensions made sense when the email platform was the database: nightly files, batch sends, one table per use case. They are the reason SFMC programs accumulate hundreds of tables nobody owns, SQL activities that join them back together, and journeys that are only as fresh as the last import. Events and profiles are how modern platforms, warehouses and event-driven architectures already work. In a follow-up article I will make that case directly: why sending communications from events is replacing data extensions, and how to migrate an SFMC program that is built on them.
Related: Object Oriented Marketing, Marketing Eventing, AMPscript 101 and Liquid 101.
Sources
- SFMC: Fire an entry event
- SFMC: Server-to-server access token
- Braze: POST /users/track
- Braze: Canvas entry properties
IDs, keys and payload values are illustrative. Sweepstakes are regulated; have counsel review your Official Rules and consent language.