Send Functions
Deliver workflow outputs to webhooks, S3 buckets, and other downstream destinations
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
| Field | Type | Required | Description |
|---|---|---|---|
functionName | string | Yes | Unique identifier for the function (per environment) |
type | string | Yes | Must be "send" |
destinationType | enum | Yes | One of "webhook", "s3", or "google_drive" |
displayName | string | No | Human-readable display name |
tags | string[] | No | Tags for organization |
The destination-specific fields below depend on destinationType.
Webhook destination
| Field | Type | Required | Description |
|---|---|---|---|
webhookUrl | string | Yes | HTTPS URL bem POSTs the payload to |
webhookSigningEnabled | boolean | No | Sign deliveries with a bem-signature HMAC-SHA256 header. Defaults to true for new Send functions. |
S3 destination
| Field | Type | Required | Description |
|---|---|---|---|
s3Bucket | string | Yes | Name 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. |
s3Prefix | string | No | Key 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 (
transformationfor extract/transform/analyze/join,routesfor classify, etc.) - A
referenceIDif 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:
| Status | What it means |
|---|---|
success | The destination accepted the payload. For webhooks: 2xx response within the timeout. For S3: PutObject succeeded. |
skip | The 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.httpStatusCodeandwebhookOutput.httpResponseBody— useful for debugging non-2xx responses. - S3 carries
s3Output.bucketNameands3Output.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 function | Subscription | |
|---|---|---|
| Where it lives | A node inside a workflow | A standalone resource that listens to a function |
| Granularity | Per workflow path; fan-out via multiple nodes | Per source function; delivery is automatic for all events |
| Mid-workflow delivery | Yes | No |
| Branching by classification | Yes (place Send behind a Classify edge) | No (subscribe-by-function only) |
| Payload reshaping | Yes (chain a Payload Shaping node before the Send) | No (delivers the event as-is) |
| Setup cost | Workflow node configuration | Single 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.