Model MatchHelp

Guides

Push records to your CRM

A push target is a URL you own. Pick any record in Model Match, or a whole search, and we send it there, shaped the way your system expects and signed so you can trust it.

Push targets are for the moment you find someone worth working and want them in your system, not ours. One click on a record, or one request for a whole list, and the data lands in your CRM, your marketing tool, or a Zapier hook. Nothing fires on its own. You decide what gets pushed, and every attempt is on record.

A record you find in Model Match, delivered to the tools you already use.

What a push target is

A push target is a saved destination with five parts:

  • A URL (HTTPS only) and a method, POST or PUT.
  • One record type it accepts: loan officer, real estate agent, mortgage company, branch, loan, property, or real estate office. A contact intake and a property feed are different targets.
  • A field mapping from our fields to the keys your system wants. A new target starts with a sensible default, so it works on first save.
  • Optional headers sent with every push, typically an Authorization header for your endpoint. Values are stored and never shown again.
  • A signing secret, shown once, so your endpoint can verify each request came from Model Match.

Targets belong to the workspace, managed by owners and admins and usable by any member, or to a single member under Personal → My push targets when someone wants their own CRM beside the shared one.

Push targets are available on Premium and Enterprise plans. A workspace that downgrades keeps its targets visible so they can be reviewed and removed.

Set one up

Go to Workspace → API → Push targets and choose Add push target.

New push target form with name, URL, method, delivery mode, request headers, and a record type picker
  1. Name and URL. The name is what members see in the “Push to…” menu.
  2. Delivery. One record per request sends each record as its own JSON body. Batches sends a JSON array of up to batchSize records per request, which is what you want for bulk pushes to an endpoint that can take a list.
  3. Request headers. Add whatever your endpoint needs to accept the call. X-Push-*, Content-Type, and Host are reserved.
  4. Record type. Pick the one entity this target accepts.
  5. Mapping. Review the default fields or edit them. Dotted keys nest, so a target of properties.email produces { "properties": { "email": … } }.
  6. Create. The signing secret appears once. Copy it into your endpoint’s configuration now. You can rotate it later from the target’s page.

Then use Send test on the target’s page. It sends a fully populated sample record through the real pipeline and records the attempt like any other push.

A push target's page showing the URL, masked signing secret with a rotate button, accepted record type, a Send test button, and the push history

What your endpoint receives

For a loan officer target mapped as

[
  { "source": "nmlsId", "target": "external_id" },
  { "source": "name", "target": "full_name" },
  { "source": "contact.workEmail", "target": "properties.email" },
  { "source": "volume", "target": "volume_12mo" }
]

a push produces

{
  "external_id": "184203",
  "full_name": "Jordan Alvarez",
  "properties": { "email": "jordan@example.com" },
  "volume_12mo": 177242593
}

A field the record doesn’t carry is left out rather than sent as null, so your “only fill if empty” rules aren’t tripped by absence. An empty mapping sends the whole record exactly as the API returns it.

Every request also carries these headers:

HeaderValue
X-Push-Signaturesha256=<hex HMAC> over <timestamp>.<body> with the target’s secret
X-Push-TimestampUnix time in milliseconds when the request was signed
X-Push-TargetThe target id, pt_…
X-Push-Entityoriginator, agent, company, branch, loan, property, or office
X-Push-Entity-IdThe record’s id (omitted on batch requests, where each element carries its own)

Bulk and batch requests add X-Push-Item-Id (stable across retries, so you can de-duplicate), X-Push-Item-Count, X-Push-Batch-Id, and X-Push-Signature-V2, an HMAC over <timestamp>.<itemId>.<body> so the item id can’t be swapped past your dedupe.

Verify the signature

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyPush(req: { headers: Headers; rawBody: string }, secret: string) {
  const timestamp = req.headers.get("x-push-timestamp") ?? "";
  const sent = req.headers.get("x-push-signature") ?? "";
  const expected = `sha256=${createHmac("sha256", secret).update(`${timestamp}.${req.rawBody}`).digest("hex")}`;

  const fresh = Math.abs(Date.now() - Number(timestamp)) < 5 * 60 * 1000;
  return fresh && sent.length === expected.length && timingSafeEqual(Buffer.from(sent), Buffer.from(expected));
}

Sign the raw request body, not a re-serialized copy. If you already verify Model Match webhooks, the same code works here.

From a search in Market Insights, Push all results to… sends every matching record. From the API, point a bulk export at the target instead of a file:

curl -X POST https://api.modelmatch.com/v1/originators/bulk-delivery \
  -H "x-api-key: mm_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "flatFilters": { "msa": "12420", "hasPhone": true },
    "period": "last12Months",
    "limit": 2000,
    "destination": { "type": "push-target", "targetId": "pt_2e64a421-c66b-49e0-a758-c51b2dda1ba5" }
  }'

The target is checked before anything is queued. A refusal comes back with a code: TARGET_NOT_FOUND, FORBIDDEN, PLAN_REQUIRED, TARGET_DISABLED, or ENTITY_MISMATCH when the target accepts a different record type. Row limits and billing are the same as a file export, one credit per row.

Once the job finishes, a worker streams the records to your endpoint:

  • A single target gets one request per record. A batch target gets arrays of up to batchSize.
  • 429, 408, 5xx, and timeouts are retried up to five times with backoff, honoring Retry-After. Any other 4xx is final on the first try.
  • Delivery is at least once. De-duplicate on X-Push-Item-Id if a repeat would matter.
  • A receiver that fails five sends in a row within two minutes trips a circuit breaker. Sends pause and resume on their own.

Progress for the job comes from POST /push-targets/batch-status with the jobId: total, sent, failed, retrying, and the ids of anything that didn’t make it.

Every attempt is on record

Each target’s page lists its last 50 sends with the status, the receiver’s response, and the exact body that was sent. Replay re-signs that stored body against the target’s current URL and secret, so a fix on your side can be tested without finding the record again. A record’s “Push to…” menu shows when it was last pushed to each target.

Permissions and scopes

WhoCan
Owners and adminsCreate, edit, delete, and rotate workspace targets
Any memberSee workspace targets and push records to them
A member, for their own targetsEverything, under Personal → My push targets
OAuth apps and MCP clientspush-targets:send to push or point a bulk job at a target, push-targets:manage to administer

Push targets or webhooks?

Push targetWebhook
What triggers itA person, or a job they submittedAn event inside Model Match
What it carriesA record, shaped by your mappingAn event envelope
When to use itGetting leads into your CRMReacting to changes, alerts, job completion

Both sign requests the same way, so one receiver can handle both.