RevOps Guide to Campaign Link Validation via API

Before any campaign URL enters your CRM as a destination — a Salesforce Campaign activity target, a HubSpot campaign URL, a Marketo landing page field — five things must pass: SSL, HTTP resolution (HTTP 200), redirect chain integrity with UTM parameters intact, Open Graph tags present for social sharing previews, and correct UTM parameter formatting. A URL that fails any of these checks will silently corrupt your attribution data, serve broken social previews, or return a 404 when a prospect clicks. Here is how to validate all five checks programmatically from any RevOps script or automation platform before the URL gets stored anywhere.

Three-card workflow: Salesforce CRM trigger card on the left, POST /v1/preflight API call card in the center, and JSON response card showing ready: true on the right, connected by dashed green arrows

The RevOps link validation gap: what CRMs store vs. what they validate

Salesforce, HubSpot, and Marketo are built to manage campaign records and track engagement. They are not built to validate destination URLs. When a RevOps engineer or marketing operations manager adds a landing page URL to a CRM campaign, the platform stores exactly what was typed — no SSL check, no HTTP resolution check, no Open Graph inspection, no UTM parameter validation.

The result: a URL that resolves to a 404, redirects through a chain that strips UTM parameters, or lacks an og:image can sit in a CRM campaign for weeks before anyone notices. By then, ad spend has been committed, email sends have gone out, and attribution reporting has a hole in it.

SSL / HTTPS
Any landing page served over plain HTTP will be flagged as insecure by modern browsers. Click-through rates drop when a browser shows a security warning. The CRM stores the URL regardless.
HTTP resolution (HTTP 200)
A URL that returns 404, 500, or redirects to an error page will still be saved as the campaign destination. The CRM has no mechanism to detect this before the campaign goes live.
Redirect chain integrity
Vanity domains, CDN rewrites, and link shorteners are common in campaign stacks. Any redirect hop that strips query strings will silently drop UTM parameters — GA4 receives organic sessions instead of the attributed campaign traffic. The CRM stores the original URL without any awareness of what happens downstream.
Open Graph tags
When a campaign URL is shared on LinkedIn, Slack, or Facebook, the platform fetches og:title, og:description, and og:image from the page. A missing og:image produces an ugly preview with no thumbnail. The CRM stores the URL without checking the page's OG metadata.
UTM parameter formatting
Case inconsistencies (utm_source=LinkedIn vs utm_source=linkedin) create separate rows in GA4, fragmenting attribution. A CRM will store either version without complaint. Consistent lowercase UTM formatting is a RevOps data quality concern, not a CRM concern.

The standard fix is a manual checklist: open the URL in a browser, run it through Facebook's Sharing Debugger, check the redirect chain in DevTools. This approach doesn't scale past five or ten URLs per week, and it depends entirely on a human remembering to run the check. For RevOps teams managing 50+ campaign URLs across multiple channels and quarters, a programmatic validation layer is the only viable path.

POST /v1/preflight: the REST API validation call

The MissingLinkz REST API exposes a single endpoint that runs all five checks — SSL, resolution, redirect chain, OG tags, and UTM integrity — in one HTTP call and returns a structured JSON response with a ready boolean. Any RevOps script, Salesforce Flow webhook handler, HubSpot custom code block, or cron job can call this endpoint directly without installing any CLI tooling.

Base URL: https://api.missinglinkz.io. Authentication: Authorization: Bearer mlz_live_.... Request body fields use source, medium, campaign — not the utm_-prefixed variants. The API assembles the full UTM-tagged URL and validates it.

curl — POST /v1/preflight
$ curl -s -X POST https://api.missinglinkz.io/v1/preflight \
  -H "Authorization: Bearer $MLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/landing","source":"linkedin","medium":"social","campaign":"spring-2026"}'

{
  "ready": true,
  "tracked_url": "https://example.com/landing?utm_source=linkedin&utm_medium=social&utm_campaign=spring-2026",
  "checks": [
    { "check": "ssl", "status": "pass", "message": "URL uses HTTPS." },
    { "check": "resolution", "status": "pass", "message": "Destination responded with 200." },
    { "check": "redirects", "status": "pass", "message": "No redirects detected." },
    { "check": "og_tags", "status": "pass", "message": "Open Graph tags present: title, description, and image." },
    { "check": "twitter_card", "status": "pass", "message": "Twitter Card present (type: summary_large_image)." }
  ],
  "summary": { "total": 12, "passed": 12, "warnings": 0, "failed": 0 },
  "recommendation": "All checks passed. Campaign link is ready to publish."
}

The response structure gives RevOps three things to work with. The ready boolean is the gate: if false, do not proceed. The checks[] array identifies exactly which check failed and why — useful for surfacing actionable error details in a Slack notification or CRM note. The tracked_url field is the validated, UTM-tagged URL ready to store as the campaign destination — no separate UTM builder step required.

For the full REST API reference including all endpoints, error codes, and authentication setup, see the MissingLinkz REST API guide. For the parent overview of all validation checks, see campaign link validation: the complete guide.

Using the ready flag as a gate in RevOps workflows

The ready boolean is designed to be a machine-readable gate. A RevOps workflow reads it and branches: if true, add the URL to the CRM campaign record; if false, surface the failure detail to the campaign manager and block the URL from being stored until the issue is resolved.

This pattern fits naturally into three common RevOps integration points:

Salesforce Flow with an external HTTP callout
A Salesforce Flow triggered on Campaign record creation can invoke an Apex HTTP callout to POST /v1/preflight. If the response ready field is false, the Flow adds a Campaign Note with the failing check details and sets a custom Link_Validation_Status__c field to Failed. If ready is true, the Flow stores the tracked_url value as the campaign's UTM destination field and sets the status to Passed. No manual check is required; the Salesforce record reflects validation state automatically.
HubSpot workflow with a custom code action
HubSpot Operations Hub workflows support JavaScript custom code actions. A workflow triggered on Campaign creation or URL property change calls POST /v1/preflight via fetch(), reads ready, and sets a HubSpot campaign property accordingly. If ready is false, the workflow enrolls the campaign manager in a notification sequence with the specific failed check as the email body. This keeps validation state visible without requiring anyone to manually re-check links.
Custom RevOps script or cron job
For teams not ready to wire validation into the CRM platform directly, a nightly Node.js or Python script that reads pending campaign records from the CRM API, calls POST /v1/preflight for each URL, and writes results back as CRM notes achieves the same outcome asynchronously. This is the lowest-friction integration pattern and the right starting point for most RevOps teams.

The key principle in all three patterns: the CRM campaign record should not be considered launch-ready until ready: true has been returned for its destination URL. This is the RevOps campaign link validation automation API equivalent of a CI build gate — the campaign does not go to launch until validation passes.

For teams already running CI/CD pipelines for marketing assets, see automating campaign link validation in CI/CD for how the same ready gate works inside GitHub Actions and GitLab CI.

Batch validation for 50+ landing pages

A single quarter's campaign calendar for a mid-market RevOps team commonly includes 50 to 200 landing page URLs across paid, email, social, and event channels. Validating these one at a time via a browser tool is not practical. The MissingLinkz REST API accepts one URL per call, but a straightforward loop in Node.js or Python can process an entire campaign inventory in under two minutes.

The following Node.js script reads an array of campaign objects, calls POST /v1/preflight for each, and logs a pass/fail summary with the tracked_url ready to copy into the CRM:

validate-campaigns.js — Node.js batch validation
const campaigns = [
  { url: "https://example.com/landing-q3", source: "linkedin",  medium: "social",  campaign: "q3-launch" },
  { url: "https://example.com/pricing",     source: "google",    medium: "cpc",     campaign: "q3-launch" },
  { url: "https://example.com/webinar",     source: "email",     medium: "email",   campaign: "q3-webinar" },
  // ... up to 200+ entries
];

const MLZ_API_KEY = process.env.MLZ_API_KEY;
const ENDPOINT = "https://api.missinglinkz.io/v1/preflight";

async function validateCampaign(campaign) {
  const res = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${MLZ_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify(campaign)
  });
  return res.json();
}

(async () => {
  const results = [];
  for (const campaign of campaigns) {
    const report = await validateCampaign(campaign);
    results.push({ ...campaign, ready: report.ready, tracked_url: report.tracked_url,
                   failed_checks: report.checks.filter(c => c.status === "fail") });
    console.log(`${report.ready ? "PASS" : "FAIL"}  ${campaign.url}`);
  }
  const failures = results.filter(r => !r.ready);
  console.log(`\nSummary: ${results.length - failures.length} passed, ${failures.length} failed`);
  if (failures.length) {
    console.log("\nFailed URLs:");
    failures.forEach(f => console.log(`  ${f.url}: ${f.failed_checks.map(c => c.check).join(", ")}`));
  }
})()

The loop runs sequentially by default, which avoids rate-limit concerns and produces predictable output. For larger batches where throughput matters, you can introduce controlled concurrency using Promise.allSettled with a concurrency limit of 5–10 parallel requests. The script above is a drop-in starting point — connect it to your CRM's export API to pull URLs and to your CRM's update API to write results back.

The equivalent in Python for teams that prefer it:

validate_campaigns.py — Python batch validation
import os, requests

MLZ_API_KEY = os.environ["MLZ_API_KEY"]
ENDPOINT = "https://api.missinglinkz.io/v1/preflight"
HEADERS = {"Authorization": f"Bearer {MLZ_API_KEY}", "Content-Type": "application/json"}

campaigns = [
    {"url": "https://example.com/landing-q3", "source": "linkedin",  "medium": "social",  "campaign": "q3-launch"},
    {"url": "https://example.com/pricing",     "source": "google",    "medium": "cpc",     "campaign": "q3-launch"},
    {"url": "https://example.com/webinar",     "source": "email",     "medium": "email",   "campaign": "q3-webinar"},
]

passed, failed = 0, 0
for c in campaigns:
    r = requests.post(ENDPOINT, headers=HEADERS, json=c).json()
    status = "PASS" if r.get("ready") else "FAIL"
    print(f"{status}  {c['url']}")
    if r.get("ready"):
        passed += 1
        print(f"  tracked_url: {r.get('tracked_url')}")
    else:
        failed += 1
        bad = [ch["check"] for ch in r.get("checks", []) if ch["status"] == "fail"]
        print(f"  failed checks: {', '.join(bad)}")

print(f"\nSummary: {passed} passed, {failed} failed")

Both scripts use only the standard library plus one HTTP client. No SDK required. The tracked_url field in each passing response is the fully assembled, validated UTM URL — the exact value to store as the CRM campaign destination. For programmatic UTM link generation patterns, see building UTM links programmatically.

REST API vs. CLI vs. MCP: which integration pattern fits your RevOps stack

MissingLinkz exposes the same campaign link validation engine through three surfaces. The right choice depends on where validation is triggered in your RevOps workflow:

REST API (POST /v1/preflight) — server-side automation
The right choice when validation is triggered by a server-side event: a CRM webhook, a Salesforce Flow callout, a HubSpot workflow custom code action, a cron job, or any script running in a cloud function. No Node.js binary required on the execution environment — any language with an HTTP client works. This is the standard integration pattern for RevOps campaign link validation automation.
CLI (mlz preflight) — terminal and CI/CD pipelines
The right choice when validation is run by a developer or engineer from a terminal, or wired into a CI/CD pipeline as a build gate. Install once with npm install -g missinglinkz, then call mlz preflight --url "..." --source "..." --medium "..." --campaign "...". Returns the same JSON response as the REST API and exits with code 1 on failure. See campaign link validation in CI/CD for pipeline integration patterns.
MCP server (mlz mcp) — AI agent workflows
The right choice when validation is called by an AI agent — Claude, Cursor, or any MCP-compatible client — as part of an automated campaign creation workflow. The MCP server exposes mlz_preflight as a tool. The agent calls the tool with URL and UTM parameters; the tool returns the same structured JSON. No human in the loop required. See the developer UTM tracking guide for how this fits into a full AI-driven campaign stack.

For most RevOps teams, the REST API and CLI are complementary. The REST API handles automated, event-driven validation (CRM triggers, nightly batch jobs). The CLI handles ad hoc validation and pre-commit or pre-deploy gates that developers or ops engineers run manually. The MCP server is additive — it extends the same validation into AI agent workflows without changing the underlying infrastructure.

FAQ

Does POST /v1/preflight build the UTM link or just validate the destination?
It does both in one call. The request body takes url, source, medium, and campaign (not utm_-prefixed). The API assembles the full UTM-tagged URL, validates the destination, and returns the assembled tracked_url along with the validation report. This means a RevOps workflow only needs one API call to both build and validate — there is no separate build step.
Do I need an API key to call the REST API?
Yes. The REST API requires a Bearer token in the Authorization header for all requests. Get an API key at missinglinkz.io — the free plan includes 1,000 validations per month. Store the key as an environment variable (MLZ_API_KEY) or as a secret in your CRM platform's credential store. Never hardcode it in source code.
How do I handle validation failures in a Salesforce Flow?
The checks[] array in the response contains an entry for each check, with a status field of "pass", "warn", or "fail", and a human-readable message. In a Salesforce Flow, deserialize the JSON response, loop over checks[], collect entries where status == "fail", and concatenate their message fields into a Campaign Note or custom field. This gives the campaign manager the exact reason the URL failed without requiring them to leave Salesforce.
Can I call the API from a HubSpot Operations Hub custom code action?
Yes. HubSpot Operations Hub custom code actions run Node.js and support fetch(). Set your MLZ_API_KEY as a HubSpot secret, pass it into the action as an input property, and call POST /v1/preflight using the fetch pattern shown in the Node.js example above. The ready boolean maps directly to a HubSpot workflow branch condition — ready === true continues, false routes to a notification action.
What is the rate limit for batch validation?
The free plan allows 1,000 validations per month. There is no per-second rate limit for sequential requests. For parallel batch scripts, keep concurrency at or below 10 simultaneous requests to avoid hitting infrastructure limits. For teams validating more than 1,000 URLs per month, the Team plan provides unlimited validations at $49/month. Check your current quota at any time by calling GET /v1/auth/status with your API key.

Add a validation layer to your RevOps campaign workflow

Stop storing unvalidated URLs in your CRM. Call POST /v1/preflight before any campaign URL enters Salesforce, HubSpot, or Marketo — get a ready boolean, a fully assembled tracked_url, and per-check failure details in a single API call.

npm install -g missinglinkz

Use the REST API from any RevOps script, Salesforce Flow, or HubSpot workflow. The same validation engine powers the CLI and MCP server — one integration, every surface.