Agent skill
Marketo Webhooks Skill
Receive and authenticate Adobe Marketo Engage webhooks sent by the "Call Webhook" step in a Smart Campaign flow. Use when building a Marketo webhook receiver, securing it with a shared secret custom header (Marketo has no HMAC signature), writing the Payload Template with lead tokens like {{lead.Id}} and {{lead.Email Address}}, handling lead_created / lead_updated deliveries, or debugging the 30-second timeout and response mappings.
Install this skill
npx skills add hookdeck/webhook-skills --skill marketo-webhooks
Marketo doesn't have webhook subscriptions. A webhook is a definition you create in Admin → Webhooks (URL, request type, payload template, encoding) and then call from a Smart Campaign with the Call Webhook flow step. Marketo's docs say to use that step in trigger campaigns only.
This differs from most providers in three ways:
- There is no signature. Marketo doesn't sign the body. There's no HMAC, signing secret or timestamp header, so don't look for an
X-Marketo-Signature. You add a secret header yourself. - You write the payload. The Payload Template is any text that works in an HTTP POST body, with tokens substituted per lead. There are no fixed event type strings, so you put an
eventname in the body yourself. - You choose the request. The method (GET or POST), token encoding (JSON or Form/Url) and headers are all set on the webhook.
When to Use This Skill
- How do I receive Marketo webhooks?
- How do I verify Marketo webhook signatures? (You can't. Send a shared secret in a custom header instead.)
- How do I set up "Call Webhook" in a Marketo Smart Campaign?
- What payload does a Marketo webhook send? (Whatever your Payload Template says.)
- How do I tell a lead-created webhook from a lead-updated one?
- Why does my Marketo webhook time out? (Marketo waits at most 30 seconds.)
Verification (core): a shared secret custom header
Open the webhook in Admin → Webhooks, choose Webhook Actions → Set Custom Header, and add a Key and Value:
X-Webhook-Secret: 3f9c0e...a long random hex value...
The header name is your choice. Custom headers can contain tokens, but a fixed random value is what you want here. Check it in constant time before you parse the body:
const crypto = require('crypto');
// NOT a Marketo signature. This is a secret YOU put in the webhook's custom headers.
function verifyMarketoSecret(headerValue) {
const expected = process.env.MARKETO_WEBHOOK_SECRET;
if (!expected) throw new Error('MARKETO_WEBHOOK_SECRET is not set'); // fail closed
if (typeof headerValue !== 'string') return false;
const a = Buffer.from(headerValue);
const b = Buffer.from(expected);
// Length check first: timingSafeEqual throws on a length mismatch.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express lowercases header names: req.get('X-Webhook-Secret') works too.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Return 401 for a missing or wrong secret and 500 when the secret isn't configured, so the endpoint never accepts everything by accident. The secret travels in a header over HTTPS, so treat it as a bearer token and rotate it by editing the webhook. See references/verification.md.
The Payload: you define it
Set Request Type POST and Request Token Encoding JSON, then write a Payload Template with a fixed event field for each webhook. Use Insert Token to add tokens. Marketo adds double quotes around string tokens itself, so don't wrap them in quotes:
{
"event": "lead_created",
"campaign": {{campaign.name}},
"lead": {
"id": {{lead.Id}},
"email": {{lead.Email Address}},
"first_name": {{lead.First Name}},
"last_name": {{lead.Last Name}},
"company": {{lead.Company Name}}
}
}
Use the same body with "event": "lead_updated" for a second webhook.
eventis this skill's convention, not a Marketo field. Marketo defines no event-type strings for webhooks.- Token names depend on your instance's fields. Pick them with Insert Token rather than typing them.
- A token value with special characters can break a JSON template, so keep Request Token Encoding on JSON and return
400for bodies that don't parse. - Treat the body as a notification. Re-fetch the lead from the Marketo REST API before you act on anything important.
Events (this skill's event values)
event | Smart Campaign trigger (example) | Fires when |
|---|---|---|
lead_created | Person is Created | A new lead enters the database and the campaign flow calls the webhook |
lead_updated | Data Value Changes | A lead field changes and the campaign flow calls the webhook |
The trigger is whatever you put on the campaign's Smart List. If an event value isn't one you handle, log it and return 200. If the body has no event field, the webhook is misconfigured, so return 400.
Delivery and Timeouts
- Marketo waits up to 30 seconds for a response, then times out. Webhook execution time directly delays the campaign: even 50 ms per call means 100,000 calls take about 1.5 hours. Respond fast and process later.
- Response mappings write response properties back to the lead, and only apply when you return a
2xx. A plain200with{"received": true}is fine. - Marketo's docs don't describe retries or a delivery ID, so dedupe on values you put in the body (e.g.
event+lead.id+campaign) or upsert onlead.id. - The URL is sent as written:
&stays&and%26stays%26.
Environment Variables
# REQUIRED. The value you put in the webhook's custom header. Generate with: openssl rand -hex 32
MARKETO_WEBHOOK_SECRET=
# OPTIONAL. The custom header name (case-insensitive). Default: X-Webhook-Secret
MARKETO_WEBHOOK_HEADER=X-Webhook-Secret
Local Development
npx hookdeck-cli listen 3000 marketo --path /webhooks/marketo
No account required. The CLI creates a guest account on first run and gives you a public HTTPS URL to paste into the webhook's URL field, plus a web UI for inspecting requests.
In production, Hookdeck's Marketo source type checks the header for you. You enter the header name and its exact value, and requests without them are rejected. See references/setup.md.
Reference Materials
- references/overview.md - How webhooks and Call Webhook work, properties, tokens, limits
- references/setup.md - Step-by-step webhook and campaign setup, custom header, testing, Hookdeck source setup
- references/verification.md - Why there is no signature, the shared-secret header pattern, gotchas