Function Types

Send Functions

Deliver workflow outputs to webhooks, S3 buckets, and other downstream destinations

Hand off to an LLM

Send functions deliver the output of an upstream workflow node to an external destination — a webhook URL, an S3 bucket, or a Google Drive folder. They're the bem-native way to wire structured data out of a workflow to your systems, without managing a separate subscription resource or polling for events.

A Send function is a node in your workflow graph like any other function. Whatever event flows into it from the previous node is delivered to the destination configured on the Send function. You can place Send nodes anywhere in a workflow — mid-graph (deliver an intermediate extract before continuing), behind a Classify branch (deliver invoices to one webhook and receipts to another), or at the terminus of a sequential pipeline.

When to Use

Use a Send function when you need to:

  • Push successful extract results to a downstream API the moment they're produced
  • Mirror every transformation into an S3 bucket for an analytics pipeline or warehouse
  • Fork delivery: send the same payload to multiple destinations by adding multiple Send nodes
  • Branch delivery: route different document types to different destinations using a Classify upstream
  • Reshape payloads for a partner-specific contract by chaining a Payload Shaping function before the Send

If your goal is "deliver every event of function X to one URL" without a workflow context, a subscription on that function is simpler. Send functions become the right tool the moment you need the destination to depend on the workflow shape — branching, fan-out, mid-graph delivery, or reshaped payloads.

Configuration Fields

Common fields

FieldTypeRequiredDescription
functionNamestringYesUnique identifier for the function (per environment)
typestringYesMust be "send"
destinationTypeenumYesOne of "webhook", "s3", or "google_drive"
displayNamestringNoHuman-readable display name
tagsstring[]NoTags for organization

The destination-specific fields below depend on destinationType.

Webhook destination

FieldTypeRequiredDescription
webhookUrlstringYesHTTPS URL bem POSTs the payload to
webhookSigningEnabledbooleanNoSign deliveries with a bem-signature HMAC-SHA256 header. Defaults to true for new Send functions.

S3 destination

FieldTypeRequiredDescription
s3BucketstringYesName of the S3 bucket bem writes to. The bucket must be in your AWS account and have a bem-issued IAM role configured for write access — contact support to enable.
s3PrefixstringNoKey prefix (folder path) under which payloads are written. The final object key combines the prefix with a bem-generated unique key per delivery.

Google Drive destination

The google_drive destination is managed through Paragon OAuth — set up the connection from the bem dashboard before creating the function. The function carries a single googleDriveFolderId field once configured.

Examples

Deliver invoice extracts to a webhook

{
  "functionName": "invoice-webhook",
  "type": "send",
  "displayName": "Send invoices to AP",
  "destinationType": "webhook",
  "webhookUrl": "https://your-app.example.com/webhooks/bem/invoices",
  "webhookSigningEnabled": true,
  "tags": ["finance", "ap"]
}

Every payload arriving at this node is POSTed to the URL above. Verifying the bem-signature header is identical to verifying signatures on subscription webhooks — see Webhooks for copy-pasteable verification code in Node, Python, and Go.

Mirror every output to S3

{
  "functionName": "archive-to-s3",
  "type": "send",
  "displayName": "Archive transformations",
  "destinationType": "s3",
  "s3Bucket": "acme-bem-archive",
  "s3Prefix": "transformations/2026/",
  "tags": ["archive"]
}

Each delivery writes one object to s3://acme-bem-archive/transformations/2026/<bem-generated-key> containing the full event payload as JSON.

What gets delivered

The body of every Send delivery is the full protocol event JSON for the upstream event — the same shape you'd retrieve from GET /v3/outputs/{eventID} or that a subscription webhook would deliver. That includes:

  • The event metadata (eventID, eventType, callID, functionCallID, workflowName, etc.)
  • The polymorphic payload for the event type (transformation for extract/transform/analyze/join, routes for classify, etc.)
  • A referenceID if one was attached to the originating call

For ad-hoc calls that take a JSON file input, the Send body is the raw input JSON. For ad-hoc calls with a binary file input, the body contains {"s3URL": "<presigned-url>"} so the receiver can fetch the file out-of-band.

Wiring a Send into a workflow

Send functions are workflow nodes. Add them to nodes and point an edge at them from whichever upstream node should feed them:

{
  "name": "invoice-pipeline",
  "mainNodeName": "invoice-extractor",
  "nodes": [
    { "name": "invoice-extractor", "function": { "name": "invoice-extractor" } },
    { "name": "send-to-ap",        "function": { "name": "invoice-webhook" } },
    { "name": "archive-to-s3",     "function": { "name": "archive-to-s3" } }
  ],
  "edges": [
    { "sourceNodeName": "invoice-extractor", "destinationNodeName": "send-to-ap" },
    { "sourceNodeName": "invoice-extractor", "destinationNodeName": "archive-to-s3" }
  ]
}

This workflow extracts an invoice once and fans the result out to both destinations in parallel.

Delivery semantics

Each Send invocation produces a send event with a deliveryStatus of success or skip:

StatusWhat it means
successThe destination accepted the payload. For webhooks: 2xx response within the timeout. For S3: PutObject succeeded.
skipThe Send node was reached but no payload was delivered (for example, an upstream error event with no successful payload to forward).

The full per-destination outcome is captured on the event:

  • Webhooks carry webhookOutput.httpStatusCode and webhookOutput.httpResponseBody — useful for debugging non-2xx responses.
  • S3 carries s3Output.bucketName and s3Output.key — the exact location the object was written to.

A failed Send delivery surfaces as an error event on the call, the same way any other function failure does. See Errors and status codes for how to read call.errors[].

Send vs Subscriptions

Both deliver event payloads to a webhook URL. They serve different shapes of the problem:

Send functionSubscription
Where it livesA node inside a workflowA standalone resource that listens to a function
GranularityPer workflow path; fan-out via multiple nodesPer source function; delivery is automatic for all events
Mid-workflow deliveryYesNo
Branching by classificationYes (place Send behind a Classify edge)No (subscribe-by-function only)
Payload reshapingYes (chain a Payload Shaping node before the Send)No (delivers the event as-is)
Setup costWorkflow node configurationSingle API call to create the subscription

If you're standing up your first webhook, start with a subscription. Add a Send function when the workflow shape — branching, fan-out, mid-graph delivery, or per-destination payloads — drives the requirement.

On this page