Skip to content

Inbound Endpoints

An Inbound Endpoint is a public URL through which another system writes records into one template — without an Omnismith access token. Stripe, GitHub, Shopify, a form tool, Zapier or n8n, a script, or a device posts its own JSON payload to the URL; Omnismith checks the delivery’s signature, maps the payload to the template’s attributes, and creates or updates the matching records.

Automations and Notification Channels send data out of a project. Inbound endpoints are the other direction: they keep a project fed by the systems where the data actually originates.

flowchart LR
A["Sender\n(Stripe, GitHub, device…)"] -->|"POST signed JSON"| B["Inbound Endpoint\n(verify signature)"]
B --> C["Mapping\n(JSON paths → attributes)"]
C -->|"create / update by external key"| D["Records\n(rules, history, metrics)"]
B --> E["Delivery Log\n(7 days, replay)"]

Each endpoint belongs to one template and has:

  • A receive URL — https://api.omnismith.io/v1/inbound/{project_id}/{endpoint_id}. The URL is not a secret; the signature authenticates every delivery.
  • A sender preset — how deliveries prove they came from your sender (Stripe’s Stripe-Signature, GitHub’s X-Hub-Signature-256, a shared secret header, …).
  • One or two secrets — two only while you rotate.
  • A mapping — where in the payload each attribute’s value lives, and which field identifies the record.
  • A delivery id source (optional) — where the sender puts its own id for a delivery, so a retried delivery is not processed twice.
  • A delivery log — every delivery from the last 7 days, with its outcome and the stored payload.

Records written by an endpoint go through the same path as any other write: attribute types are checked, the template’s Rules are enforced, every change lands in the record’s history, and metric readings are appended to their time series. History shows the author as Inbound · endpoint name, so you can always tell which records came from which integration.

Records are matched to the sender’s ids through External Keys: the mapping names the field that carries the sender’s id (cus_123, a hostname, a device serial), and the same id always resolves to the same record.


Endpoints are configured on the template, in the template editor’s Inbound tab.

  1. Navigate to Blueprints → Template Library and open the template (e.g. Server).
  2. Open the Inbound tab and click Add inbound endpoint.
  3. Under Connection, enter a Name and pick the Sender. For a sender that signs with its own secret (Stripe, Shopify, Standard Webhooks, Lemon Squeezy), paste the signing secret it shows you; otherwise leave the secret empty and one is generated.
  4. Under Mapping, choose the mode, the external key path, and a source for each attribute you want to fill (see Mapping a Payload).
  5. Click Create endpoint. A one-time dialog shows the Receive URL and the Secret — copy both into the sender’s webhook settings now.

The Inbound tab of the Server template, listing two endpoints with their sender, last delivery and a "Signature failing" warning

Each row shows the sender, an Accept deliveries toggle, the outcome and time of the last delivery, and buttons for the delivery log, editing and deleting. A Signature failing badge means a delivery failed signature verification after the last one that was processed — the sender is almost always using the wrong secret or header.

A disabled endpoint answers every delivery with 404. Deleting an endpoint stops its URL from accepting deliveries; records it already wrote are kept.

The Connection section of the endpoint dialog: name, sender preset, header, receive URL and secret hints with Add secret


Pick the preset that matches your sender. A preset fixes the signature header, format and algorithm, and fills in the sender’s delivery id when it has one.

Sender presetSignature headerSecretDelivery id
GitHubX-Hub-Signature-256Generated by Omnismith, or the one you set in the GitHub webhookheader X-GitHub-Delivery
StripeStripe-Signature (timestamped, 5-minute window)The endpoint’s signing secret from Stripe (whsec_…)payload $.id
ShopifyX-Shopify-Hmac-Sha256The app’s webhook secretheader X-Shopify-Webhook-Id
Standard Webhookswebhook-signature (symmetric v1, timestamped)The sender’s whsec_… secret, or generatedheader webhook-id
Lemon SqueezyX-SignatureThe webhook’s signing secret—
Secret headerX-Omnismith-Secret by default, configurable, with an optional value prefix (e.g. Authorization + Bearer )Always generated— (set one yourself)
Custom HMACAny header: algorithm (SHA-1/256/512), hex or base64, whole value / key=value list / versioned list, signed content over {body}, {timestamp} and {id}, replay windowGenerated or provided— (set one yourself)

Use Secret header for senders that cannot sign a payload — Zapier, Make, n8n, shell scripts, microcontrollers. The sender simply includes the secret in a header on every request.

The secret is displayed exactly once — in the dialog after you create the endpoint or add a secret — and is never shown again. Afterwards the endpoint only lists the last four characters and the date each secret was added. If a secret is lost, add a new one.

The one-time secret dialog showing the receive URL and the secret with copy buttons

An endpoint holds at most two secrets, and deliveries signed with either are accepted:

  1. In the endpoint’s Connection section, click Add secret (paste the sender’s new secret, or leave it empty to generate one) and copy it from the one-time dialog.
  2. Switch the sender to the new secret.
  3. Click Remove on the old secret. An endpoint always keeps at least one.

The mapping turns each delivery’s JSON into one or more records.

The Mapping section: mode pills, external key path, items path, match conditions and one row per template attribute

ModeBehaviour
Create or update (upsert)Finds the record by its external key and updates it, or creates it with that key. Retries and repeated events always land on one record. Recommended.
Always create (create)Every delivery creates new records.
Update only (update)Only updates records found by external key; a key no record holds is a record error.
  • External key path — where the sender’s id for the record is, e.g. $.data.object.id. An optional Key prefix (e.g. stripe:) namespaces keys when several senders feed one template.
  • Items path — optional path to a list, e.g. $.readings. Each element becomes its own record (at most 100 per delivery). Inside an item, $ is the element and $root is the whole payload.
  • Match conditions — optional conditions on the whole payload, all of which must hold (equals, does not equal, is one of, exists, does not exist). Use them to accept only the event types you care about; any other delivery is acknowledged and skipped. Values compare strictly: "1" is not 1.

Paths support $, .name, ['name'] and [index] — for example $.data.object.email or $.items[0].sku.

The mapping lists every attribute of the template. For each one, choose a Source:

  • Not mapped — deliveries never touch it.
  • From payload — a Path, plus an optional Default (used when the path is absent or null) and When null (Keep value, the default, or Clear value).
  • Constant — a fixed value written on every delivery (e.g. a source attribute set to stripe).

How values are resolved:

Attribute kindResolution
Text, number, booleanTaken as sent.
ListMatched to an option by label, case-insensitive (or by option id). Options are never created; an unknown label is a record error.
ReferenceThe value (after an optional Key prefix) is looked up as an external key of the referenced template.
Date / datetimeRead like any date or datetime value (no offset means UTC), or parsed with a Format: ISO 8601, epoch seconds or epoch milliseconds.
MetricA number, appended as a reading. Observed at (path) and Time format take the reading’s time from the payload; without it, the receive time is used.

The Test section runs the mapping — including unsaved changes — on a sample payload and shows what each record would become. Nothing is written. Paste a sample event from the sender’s webhook documentation, or click Use a recent delivery to load one from the log, then click Preview.

The Test section: a sample payload with two readings, previewed as an update of api-prod-01 and a create of api-prod-04 with metric values and observation times

Each record shows its external key, whether it would be a Create or an Update, and the resolved values. Errors (an unknown list label, a rule that would be violated, a value of the wrong type) appear next to the attribute they belong to.

The Delivery id section names where the sender puts its own id for a delivery — a header or a payload path. Presets fill it in. A delivery whose id was already processed in the last 7 days is acknowledged and skipped, so a sender’s retry is never applied twice.


StatusBody statusMeaningSender should
200processedEvery record was written.—
200partialSome records of a multi-record delivery failed; errors and items say which.Not retry — fix the mapping and replay the delivery.
200skippedNothing was written: reason is match (conditions did not hold), no_items (empty list) or duplicate (delivery id already processed).—
400—The body is not a JSON object or array.Fix the payload.
401—The signature is missing or wrong; reason is missing_signature, malformed_signature, mismatch or stale_timestamp.Fix the secret or header.
404—No enabled endpoint at this URL.Check the URL and the Accept deliveries toggle.
413—The body is over 1 MB.Split the delivery.
422—No record could be written; errors are keyed per record, e.g. items[0].attributes.tier.Fix the mapping or the payload.
409, 429, 5xx—The delivery could not be written right now (a key conflict, a quota or rate limit).Retry later.

Every answer after signature verification carries log_id, the delivery’s row in the endpoint’s delivery log, so a sender-side log entry can be matched to the Omnismith one:

{
"status": "partial",
"entity_ids": ["01a0f0e2-7c1a-7000-8000-000000000021", "01a0f0e2-7c1a-7000-8000-000000000023"],
"items": [
{ "index": 0, "entity_id": "01a0f0e2-7c1a-7000-8000-000000000021", "created": false, "error": null },
{
"index": 1, "entity_id": null, "created": null,
"error": {
"type": "error/validation", "title": "Validation Failed", "status": 422,
"detail": "key: unknown_key: no record holds the external key 'api-prod-03', and mode 'update' does not create one.",
"errors": { "key": ["unknown_key: no record holds the external key 'api-prod-03', and mode 'update' does not create one."] }
}
},
{ "index": 2, "entity_id": "01a0f0e2-7c1a-7000-8000-000000000023", "created": false, "error": null }
],
"errors": { "items[1].key": ["unknown_key: no record holds the external key 'api-prod-03', and mode 'update' does not create one."] },
"log_id": "01a0f0e2-7c1a-7000-8000-0000000000d1"
}

A partial delivery answers 200 on purpose. Retrying it would re-send the records that already succeeded — and duplicate their metric readings — while the failed ones are almost always mapping problems that no retry fixes. They stay in the log until you replay them.


Click the history icon on an endpoint’s row to open its Delivery log. It lists every delivery from the last 7 days, newest first: received time, outcome (Processed, Partial, Skipped, Rejected, Failed), HTTP status, delivery id, number of records and duration. Filter by outcome and by date from the toolbar.

Selecting a delivery shows its record errors, links to the records it wrote, the stored headers (content type, user agent and the delivery id header) and the pretty-printed body.

The delivery log with a partial delivery selected: one record error for an unknown external key, links to the two records written, headers, body and a Replay button

Replay re-runs a stored delivery through the endpoint’s current mapping and writes the records — the way to recover after you fix the mapping or the template:

  • It is offered for Rejected, Failed and Partial deliveries. For a partial one, only the records that failed are re-run.
  • The replay gets its own row in the log, marked with a replay icon, recording which delivery it replays and who asked for it. Records are still attributed to the endpoint.
  • A delivery without a stored body cannot be replayed: signature failures are logged without one, and bodies over 256 KB are stored truncated.
  • Processed and skipped deliveries are not replayed; to apply one again, re-send it from the sender.

Signature failures are logged too, without their body and at most one row per endpoint every 10 seconds, so a wrong secret is visible without unauthenticated traffic filling the log.


A Customer template with email (text), name (text) and tier (list: Free, Pro, Team). Create an endpoint with the Stripe preset, paste the endpoint’s signing secret from the Stripe dashboard, and use this mapping:

{
"mode": "upsert",
"match": [{ "path": "$.type", "operator": "in", "value": ["customer.created", "customer.updated"] }],
"key": { "path": "$.data.object.id", "prefix": "stripe:" },
"attributes": {
"email": { "path": "$.data.object.email" },
"name": { "path": "$.data.object.name" },
"tier": { "path": "$.data.object.metadata.tier", "default": "Free" }
}
}
  • customer.created creates the record with the key stripe:cus_…; every later customer.updated updates the same record.
  • Other Stripe event types are acknowledged and skipped by the match condition.
  • The preset reads Stripe’s event id ($.id) as the delivery id, so Stripe’s automatic retries are never applied twice.
  • A tier label that is not one of the list’s options becomes a record error, visible in the log; add the option, then replay.

A device posting a batch of readings → Server template

Section titled “A device posting a batch of readings → Server template”

A monitoring agent that cannot sign payloads posts a batch of readings every minute. Use the Secret header preset and this mapping:

{
"mode": "upsert",
"items": "$.readings",
"key": { "path": "$.host" },
"attributes": {
"name": { "path": "$.host" },
"cpu_usage": { "path": "$.cpu", "timestamp": { "path": "$.ts", "format": "epoch_ms" } },
"memory_usage": { "path": "$.mem", "timestamp": { "path": "$.ts", "format": "epoch_ms" } },
"disk_usage": { "path": "$.disk", "timestamp": { "path": "$.ts", "format": "epoch_ms" } },
"last_check_at": { "path": "$.ts", "format": "epoch_ms" }
}
}

with the delivery id read from the payload ($.batch_id). The agent sends:

Terminal window
curl -X POST "https://api.omnismith.io/v1/inbound/$PROJECT_ID/$ENDPOINT_ID" \
-H "Content-Type: application/json" \
-H "X-Omnismith-Secret: omni_in_demo_secret_key_12345" \
-d '{
"batch_id": "b-0005",
"readings": [
{ "host": "api-prod-01", "ts": 1790934016000, "cpu": 39.6, "mem": 62.8, "disk": 71.2 },
{ "host": "db-prod-01", "ts": 1790934016000, "cpu": 70.3, "mem": 82.0, "disk": 88.8 }
]
}'

Each reading updates the server whose external key is its hostname — or creates it the first time a new host reports — and appends CPU, memory and disk readings at the time the agent measured them, not the time they arrived.


LimitValue
Body size1 MB per delivery (413 above)
Records per delivery100 items (422 too_many_items above)
Attributes per mapping100
Delivery rateAbout 10 deliveries per second per endpoint, with short bursts absorbed (429 above)
Secrets per endpoint2 (during rotation)
Delivery log & duplicate detection7 days
Stored body for replayFirst 256 KB

Records, updates and metric readings written by an endpoint count toward your plan exactly as if they were written through the API — see Billing and Usage. When a quota is exhausted, deliveries are answered with 429 so the sender retries later.


Every operation in this page is also available to AI agents through the MCP connector: an agent that scaffolds a template can create its endpoint (create_template_inbound_endpoint), check the mapping on a sample event (preview_inbound_mapping), hand you the URL and secret once, and after a test event read the log (list_inbound_deliveries, get_inbound_delivery), fix the mapping and replay_inbound_delivery. get_schema_overview lists each template’s endpoints.