Skip to main content

Inbound Sources

Push data from Stripe, Typeform, or any webhook into Coeffection without handing out an API key. Each source is a signed, per-tenant endpoint that can only stage events โ€” your own workflows decide what becomes a Contact, a Lead, or anything else.

How it works#

An external system signs a payload and POSTs it to your source's endpoint. Coeffection verifies the signature and writes the raw event to a staging table โ€” it never creates CRM records directly. A workflow you author (triggered by External event) then maps the staged payload onto entities. You choose whether that happens instantly (Auto) or after a human reviews it (Review).

1 ยท Stage
Signed POST โ†’ raw event saved (no entity written)
2 ยท Dispatch
Your external_event workflow runs the mapping
3 ยท Create
Contact, Lead, โ€ฆ created via your rules
โ„น
Why not an API key? A raw write API is a broad, hard-to-revoke attack surface. Inbound Sources keep external systems sandboxed to a staging table; the mapping logic lives in yourtenant's workflows, versioned and revocable per source.

Create a source#

Go to Admin โ†’ Integrations โ†’ Inbound Sources โ†’ New.

1

Name & mode

Give the source a name (the slug is generated and is permanent). Pick Auto to process events on arrival, or Review to hold them in an inbox for a person to approve.
2

Target workflow

Choose the workflow that maps this source's events. Only workflows whose trigger is External event appear here โ€” if you have none yet, the dropdown links you to create one.
3

Dedupe key

A JSON path to a unique id in the payload (default id). Replays of the same id are ignored, so a retrying sender never double-creates records.
4

Copy the signing secret

On save, the signing secret is shown once. Copy it into your sender's config โ€” you can't see it again (rotate to get a new one).

Admin โ†’ Integrations โ†’ Inbound Sources

NameSlugModeWorkflow
Stripe โ€” App Carestripe-app-careAutoPurchase โ†’ Lead
Typeform โ€” Demo reqtypeform-demoReviewDemo โ†’ Contact
โš 
The signing secret is encrypted at rest and shown exactly once. If you lose it, use Rotate secret โ€” this immediately invalidates the old one, so update your sender at the same time.

Describe the payload shape#

On the source's Payload Shape tab, paste a sample event and click Detect fields. Coeffection walks the JSON (including nested objects via dot-paths like customer.email) and lets you mark which fields show in the Review inbox and which is the dedupe key. The full raw payload is always stored โ€” this just drives the inbox columns and the $event.* references your workflow can use.

Sign your requests#

Every request is authenticated with an HMAC-SHA256 signature over <timestamp>.<raw body>, sent in an x-webhook-signature header as t=<unix_seconds>,v1=<hex>. The timestamp must be within ยฑ5 minutes (replays are rejected). The source editor gives you a copy-paste snippet; here are the essentials:

Node.js
import crypto from "node:crypto";

const SECRET = process.env.COEFFECTION_SIGNING_SECRET; // shown once on create
const ENDPOINT =
  "https://app.coeffection.com/api/ingest/<tenantId>/<sourceSlug>";

const body = JSON.stringify({ id: "evt_123", email: "ada@acme.com" });
const t = Math.floor(Date.now() / 1000); // unix seconds
const v1 = crypto
  .createHmac("sha256", SECRET)
  .update(`${t}.${body}`) // sign "<timestamp>.<raw body>"
  .digest("hex");

await fetch(ENDPOINT, {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-webhook-signature": `t=${t},v1=${v1}`,
  },
  body,
});
Python
import hashlib, hmac, json, time, requests

SECRET = "..."  # shown once on create
ENDPOINT = "https://app.coeffection.com/api/ingest/<tenantId>/<sourceSlug>"

body = json.dumps({"id": "evt_123", "email": "ada@acme.com"})
t = int(time.time())
v1 = hmac.new(SECRET.encode(), f"{t}.{body}".encode(), hashlib.sha256).hexdigest()

requests.post(
    ENDPOINT,
    headers={"content-type": "application/json", "x-webhook-signature": f"t={t},v1={v1}"},
    data=body,
)

Sign the exact bytesyou send โ€” don't re-serialize the JSON, since key order and spacing change the signature.

Map events with a workflow#

In the workflow builder, choose the External event trigger and pick this source. Then add Create entity actions that read payload values with $event.<path>. To link records, give a node a Store as name and reference its id downstream with $<name>.id.

Example: a purchase becomes a Contact + Lead

TriggerExternal event โ†’ stripe-app-care
Create Contactemail = $event.customer.email ยท store as "contact"
Create Leadcontact = $contact.id ยท amount = $event.amount
โ„น
The same mechanism works for any chain โ€” create a Company, store it, then attach Contacts and Opportunities that reference $company.id.

Auto vs. Review#

Autoย Hands-off

The workflow runs the instant a signed event arrives. Best for trusted, high-volume sources like completed purchases.

Reviewย Human-in-the-loop

Events wait in the Inbound Inbox with your chosen columns. A teammate clicks Promote to run the workflow or Reject to discard. Best for demo requests, form fills, or anything needing a glance first.

Security & safeguards#

  • Staging-only by construction. The ingress endpoint physically cannot write CRM records โ€” only your workflow can, under your rules.
  • System fields are off-limits. A payload can never set ownership or identity columns (tenant, owner, account, status, record id) โ€” those are managed by Coeffection.
  • Tenant-scoped.The endpoint carries your tenant in the URL; a payload can't reassign an event to another tenant.
  • Per-event cap.One event can only create a bounded number of records, so a replay storm can't exhaust your data.
  • Revocable. Rotate a secret or deactivate a source at any time; in-flight requests with the old secret stop working immediately.
โœ“
Unknown source, bad signature, stale timestamp, or replay all return the same 401โ€” there's no way to probe which tenants or sources exist.