Connectv1

Flow triggers and inputs

Start flows from signed webhooks, provider-signed webhooks from Shopify, Stripe, GitHub, HubSpot, Adobe Commerce and VTEX, approved file uploads, and two-step chained actions.

Updated 09/10/2026

On this page

Ways records reach a flow

Besides schedules and manual runs, a flow can take records from outside Nexra:

InputUse it when
Signed webhookYour own system can sign a request with Nexra's nexra_v1 scheme.
Provider-signed webhookShopify, Stripe, GitHub, HubSpot, Adobe Commerce or VTEX sends events in its own signed format.
Approved file uploadSomeone exports an approved CSV or JSON file and uploads it to start a run.

Every input is checked in full against the flow's record schema before anything is kept, stored as a workspace file for 7 days by default (up to 30 for webhook triggers) so a run can be retried, and recorded on the evidence chain by its SHA-256 only. Each accepted input starts an ordinary run, so every write still goes through the governed-action gate.

The flow's How records arrive panel shows what it accepts and lists recent inputs and deliveries.

Signed webhooks

Create a trigger

Publish the flow, then choose Create a webhook trigger, or call the API. Creating triggers needs connect.schedule.manage.

http
POST /api/console/connect/flows/<flow id>/triggers
Content-Type: application/json

{ "name": "Rostering system", "environment": "sandbox" }
json
{
  "triggerId": "<trigger id>",
  "flowId": "<flow id>",
  "kind": "webhook",
  "name": "Rostering system",
  "status": "active",
  "environment": "sandbox",
  "url": "/api/connect/triggers/ftp_Qm3v…",
  "secretLast4": "x9Ka",
  "maxBodyBytes": 65536,
  "maxDeliveriesPerMinute": 60,
  "timestampToleranceSeconds": 300,
  "inputRetentionDays": 7,
  "verification": "nexra_v1",
  "signing": {
    "headers": ["X-Nexra-Event-Id", "X-Nexra-Timestamp", "X-Nexra-Signature"],
    "algorithm": "HMAC-SHA256",
    "signedString": "v1:{timestamp}:{eventId}:{raw body}",
    "signatureFormat": "v1={hex}"
  },
  "secret": "nxwhsec_…",
  "secretShownOnce": true
}

The secret is shown once. Store it in the sending system's secret store; afterwards Nexra shows only its last four characters. It is held in a secure vault, never in the database or the URL. The URL carries only an unguessable trigger ID.

You can set maxBodyBytes (up to 100 KiB), maxDeliveriesPerMinute (up to 600), timestampToleranceSeconds (30 to 900) and inputRetentionDays (up to 30) when you create the trigger.

Send a delivery

http
POST /api/connect/triggers/ftp_Qm3v…
Content-Type: application/json
X-Nexra-Event-Id: evt_visits_2026-10-09_0001
X-Nexra-Timestamp: 1791532800
X-Nexra-Signature: v1=29eade3aea26da9593c48a5fc620d1f036d780fd8406c5324c1540fa12bd39e1

{"records":[{"id":"V-1042","clientId":"C-311","serviceDate":"2026-10-08","description":"Home visit, 1 hour","amount":42.5,"approvalStatus":"approved"}]}
HeaderValue
X-Nexra-Event-IdUnique per event: 8 to 128 characters of A-Z a-z 0-9 . _ : -
X-Nexra-TimestampUnix seconds when you signed
X-Nexra-Signaturev1= followed by the hex HMAC-SHA256 of the signed string

The signed string is v1: + timestamp + : + event ID + : + the raw body bytes, keyed with the trigger secret. The example above was signed with the placeholder secret nxwhsec_REPLACE_WITH_YOUR_TRIGGER_SECRET; use it to check your own code produces the same signature.

javascript
import crypto from "node:crypto";

export function signNexraDelivery(secret, eventId, rawBody) {
  const timestamp = Math.floor(Date.now() / 1000);
  const signature = crypto
    .createHmac("sha256", secret)
    .update(`v1:${timestamp}:${eventId}:`)
    .update(rawBody)
    .digest("hex");
  return {
    "Content-Type": "application/json",
    "X-Nexra-Event-Id": eventId,
    "X-Nexra-Timestamp": String(timestamp),
    "X-Nexra-Signature": `v1=${signature}`,
  };
}
python
import hashlib, hmac, time

def sign_nexra_delivery(secret: str, event_id: str, raw_body: bytes) -> dict:
    timestamp = int(time.time())
    signed = f"v1:{timestamp}:{event_id}:".encode() + raw_body
    signature = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return {
        "Content-Type": "application/json",
        "X-Nexra-Event-Id": event_id,
        "X-Nexra-Timestamp": str(timestamp),
        "X-Nexra-Signature": f"v1={signature}",
    }

Sign the exact bytes you send. The body can be one record, a list of records, or an object whose records field (set on the flow, for example records) holds the list, with up to 500 records per delivery.

Responses

StatusMeaning
202Accepted. A run is queued: {"accepted": true, "deliveryId": "…", "executionId": "…", "payloadSha256": "…", "records": 1}
200The same event ID and body were already accepted. It is not run again.
401Missing or invalid signature, or the timestamp is outside the window (300 seconds by default).
404Unknown or disabled trigger.
409The event ID was reused with a different body, or the flow has no published webhook version.
413The body is larger than the trigger's limit (64 KiB by default).
415The body is not application/json.
422The records do not match the flow's schema. The response names the record and field, never the value.
429Over the trigger's rate limit (60 deliveries a minute by default), or too many refused requests from your address.
503Temporarily unavailable. Retry with the same event ID.

Retrying with the same event ID is always safe: each event ID is single use per trigger and remembered for 7 days. The rate limit counts only correctly signed deliveries, so knowing a trigger URL is not enough to use up its quota.

Rotate and disable

Rotate issues a new secret. Pass {"graceMinutes": 60} (up to 1,440) to keep the old secret valid while senders switch; during that window, either signature is accepted. Disable stops the trigger. Deliveries shows recent attempts with their status and payload SHA-256 only.

MethodPath
POST/api/console/connect/flows/:flowId/triggers
GET/api/console/connect/flows/:flowId/triggers
POST/api/console/connect/triggers/:triggerId/rotate
POST/api/console/connect/triggers/:triggerId/disable
GET/api/console/connect/triggers/:triggerId/deliveries

Provider-signed webhooks

Set verification when you create the trigger to accept a provider's own signature, with no relay in between. Send the secret the provider signs with once, as providerSecret (for Adobe, the clientId). It goes straight to a secure vault and is never returned, logged or placed in a URL. The scheme cannot change after creation.

http
POST /api/console/connect/flows/<flow id>/triggers
Content-Type: application/json

{
  "name": "Shopify orders",
  "verification": "shopify",
  "providerSecret": "<your Shopify app client secret>",
  "shopDomain": "example-store.myshopify.com",
  "eventTypes": ["orders/create"]
}

eventTypes (optional, up to 20) keeps only those topics; other events are answered 200 and ignored, so the provider does not retry them.

verificationProof of originReplay protectionRecords come from
shopifyX-Shopify-Hmac-Sha256: base64 HMAC-SHA256 of the raw body with your app's client secret. Optional shopDomain must match.Single-use X-Shopify-Webhook-Id; X-Shopify-Triggered-At at most 24 hours oldThe body
stripeStripe-Signature t=…,v1=…: hex HMAC-SHA256 of t.body with your whsec_ endpoint secret.Signed timestamp within the trigger's window; single-use event IDdata.object
githubX-Hub-Signature-256: sha256= hex HMAC-SHA256 of the body.Single-use X-GitHub-Delivery, kept 30 daysThe body; ping is acknowledged without a run
hubspot_v3X-HubSpot-Signature-v3: base64 HMAC-SHA256 of method, full trigger URL, body and timestamp with your app's client secret.X-HubSpot-Request-Timestamp within 5 minutes; single-use event IDsThe list of events
adobe_io_eventsTwo RSA-SHA256 digital signatures, checked against Adobe's published public keys; the recipient client ID must match.Single-use eventid, kept 30 daysdata.value for Adobe Commerce, otherwise data
vtexVTEX hooks are unsigned, so Nexra issues a secret that the hook sends in X-Nexra-Hook-Token through hook.headers.Single-use order ID, state and last change, kept 30 daysThe body

For Adobe Commerce, use Adobe I/O Events (for example observer.sales_order_place_after); Nexra answers the registration challenge for you. For VTEX, Nexra generates the secret and shows it once; the token proves the sender holds it. GitHub and VTEX send no signed timestamp, so their replay protection rests on the single-use delivery key, kept for 30 days.

Accepted provider deliveries are answered 200, as Shopify and VTEX require. Repeats are answered 200 without running again, even when the provider changed delivery details in the body. Secrets in URLs are never accepted. For a sender that cannot sign in one of these ways, place a small relay in front that re-signs with nexra_v1.

Rotate works for provider schemes too: send the provider's new providerSecret with an optional grace period, matching the rolling secrets Stripe and others offer.

Approved file uploads

When a person exports an approved file, upload it and start a run with it. In the console, open the flow and choose Upload approved export. Uploading and running needs connect.flow.execute (Owner, Admin, Developer and Connect operator).

http
POST /api/console/connect/flows/<flow id>/inputs?fileName=visits.csv
Content-Type: text/csv

id,clientId,serviceDate,description,amount,approvalStatus
V-1042,C-311,2026-10-08,"Home visit, 1 hour",42.50,approved
V-1043,C-207,2026-10-08,Medication check,18.00,approved
json
{
  "inputId": "<input id>",
  "flowId": "<flow id>",
  "channel": "file",
  "format": "csv",
  "fileName": "visits.csv",
  "sizeBytes": 141,
  "sha256": "e4b0…",
  "recordCount": 2,
  "executionId": null,
  "createdAt": "2026-10-09T09:02:17.310Z",
  "expiresAt": "2026-10-16T09:02:17.310Z"
}

Then start the run with the returned inputId. One upload starts one run.

http
POST /api/console/connect/flows/<flow id>/executions
Content-Type: application/json

{
  "environment": "sandbox",
  "inputId": "<input id>",
  "idempotencyKey": "visits-2026-10-09",
  "correlationId": "visits-2026-10-09"
}

CSV rules

CSV is parsed strictly, so a file is either read exactly as written or refused with a clear reason:

UTF-8, with or without a byte-order mark.

A header row of unique field names that includes every required field.

Comma delimiters, double-quote quoting and "" for a quote inside a quoted cell.

CRLF or LF line endings; one trailing line break is fine, blank lines elsewhere are not.

The same number of cells in every row.

Cells convert to the field's type (number, integer, boolean or date). An empty cell is treated as no value.

No spreadsheet formulas: by default a text cell starting with =, +, -, @ or a tab is refused, so nothing reaches the destination as a live formula.

Up to 10,000 rows per file, and 5 MiB by default (a flow can allow up to 20 MiB).

Errors name the line and column, never the cell's content. JSON uploads follow the same schema and can be one record, a list, or an object holding the list.

Two-step chained actions

A destination step can use the result of an earlier destination step, for example a purchase-order line written to the header just created:

json
{ "chain": { "fromStep": "po_header", "bind": [{ "from": "id", "to": "orderId" }] } }

The second step runs after the first, and only for records the first step completed.

Each write has its own approval. An approval binds the exact action, and the second write includes the first write's result, which only exists once the first write has run.

When the first write waits for approval, approving it writes it and then requests the second write's approval automatically, bound to the real result. Nobody has to retry the record, and this survives a restart.

If the second write fails after the first succeeded, the record waits in the dead-letter queue on the second step with a note naming what the first step created. Retrying writes against the same result. Nexra never reverses the first write automatically.

Chains are two steps deep. Use separate flows for longer sequences.

Next

Governed workflow templates: templates that use uploads, signed webhooks and chained steps.

Runs, monitoring and recovery: follow the run each input starts.

Need a hand?

Ask Nexra Agent for implementation steps or error guidance.

Ask Nexra Agent