# Webhooks

Webhooks connect Bento workflows to the rest of your stack. Use a workflow **Send webhook** action when Bento should notify another system, such as a CRM, fulfillment tool, internal app, or support queue.

If another system needs to send data **into** Bento, use the [Events API](/docs/events_api). That keeps inbound data in the same event model that powers segments, workflows, and reporting.


> ⚠️ **Warning**
> Bento does not currently expose account-level outbound event subscriptions in these docs. This page covers the workflow Send webhook action and inbound event tracking through `/batch/events`.


## Available Patterns


## Send Webhooks From Workflows


  
    Add **Send webhook** when a workflow reaches the point where an external system should act. Common examples include:

    - Sending a high-intent lead to your CRM.
    - Creating a fulfillment task after a purchase event.
    - Posting a trial conversion to an internal reporting service.
    - Notifying your app that Bento added or removed a lifecycle tag.

    Configure the action with the destination URL and the JSON body your app expects. Use Liquid variables when the payload needs data from the person, event, site, or workflow context.

    
> Use a workflow webhook for actions that should happen because a person reached a specific step. Use the Events API for external systems that need to create events in Bento.

  
  
    ### Action Fields

    
      - **url** (`string`): 
        The public HTTPS endpoint Bento should call.
      
      - **method** (`string`): 
        Use `POST` unless your endpoint explicitly expects another method.
      
      - **body** (`object`): 
        JSON payload sent to your endpoint. Build this from static values and Liquid variables.
      
      - **headers** (`object`): 
        Optional request headers your endpoint needs, such as a shared secret.
      
    
  


### Example payload


```json {{ title: 'JSON body' }}
{
  "event": "bento.workflow.step_reached",
  "workflow": "{{ workflow.name }}",
  "email": "{{ visitor.email }}",
  "person": {
    "first_name": "{{ visitor.first_name }}",
    "tags": "{{ visitor.tags }}"
  },
  "source_event": {
    "type": "{{ event.type }}",
    "details": {{ event.details | json }}
  }
}
```

```js {{ title: 'Express receiver' }}
app.post('/webhooks/bento/workflow', express.json(), async (req, res) => {
  const sharedSecret = req.get('x-bento-webhook-secret')

  if (sharedSecret !== process.env.BENTO_WEBHOOK_SECRET) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  await crm.upsertLead({
    email: req.body.email,
    source: req.body.workflow,
    lastBentoEvent: req.body.source_event?.type,
  })

  return res.status(200).json({ ok: true })
})
```


## Receive Events In Bento

When a third-party system sends data to you first, forward the important event to Bento through `/batch/events`.


```bash {{ title: 'cURL' }}
curl -X POST 'https://app.bentonow.com/api/v1/batch/events?site_uuid=YOUR_SITE_UUID' \
  -u 'PUBLISHABLE_KEY:SECRET_KEY' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: YourApp/1.0' \
  -d '{
    "events": [{
      "type": "crm.deal_won",
      "email": "customer@example.com",
      "fields": {
        "plan": "growth",
        "customer": true
      },
      "details": {
        "deal_id": "deal_123",
        "value": {
          "currency": "USD",
          "amount": 3000
        }
      }
    }]
  }'
```

```js {{ title: 'Node SDK' }}
await bento.V1.track({
  type: 'crm.deal_won',
  email: 'customer@example.com',
  fields: {
    plan: 'growth',
    customer: true,
  },
  details: {
    deal_id: 'deal_123',
    value: {
      currency: 'USD',
      amount: 3000,
    },
  },
})
```


Events can create new people, update fields, and trigger workflows. For the full request shape, see the [Events API reference](/docs/events_api).

## Payload Design

Good webhook payloads are boring and predictable. Keep the top-level keys stable, put nested business data under `details`, and include an id from your system when the action can run more than once.


  - **event** (`string`): 
    A stable name for the action, such as `bento.workflow.step_reached`.
  
  - **email** (`string`): 
    The person this workflow step is about. Use the email as the lookup key in most external systems.
  
  - **workflow** (`string`): 
    The workflow or step name that sent the webhook.
  
  - **details** (`object`): 
    Extra data your app needs. Keep it JSON serializable.
  
  - **external_id** (`string`): 
    Optional id from your app or CRM. Use it to make your receiver idempotent.
  


## Testing And Debugging

- Start with a test workflow and a seed contact you control.
- Use a service like [webhook.site](https://webhook.site) to inspect the first payload before pointing Bento at production.
- Return a `2xx` status quickly. Do slow work in your own queue after receiving the request.
- Log the request body, response status, and your own correlation id.
- If a workflow webhook does not arrive, check that the workflow is active, the person reached the step, the URL is public, and the endpoint accepts JSON.

## Security Checklist

- Use HTTPS for every destination URL.
- Add a shared secret header and reject requests without it.
- Treat all incoming webhook JSON as untrusted input.
- Avoid sending API keys, passwords, or payment data in the payload.
- Make your receiver safe to run twice. Network retries and manual replays can happen in any webhook system.

## Related Docs

- [Workflows](/docs/concepts/workflows) for the automation model.
- [Events API](/docs/events_api) for sending inbound events to Bento.
- [API authentication](/docs/authentication) for REST API credentials.
- [Troubleshooting](/docs/troubleshooting#integration-issues) for common integration checks.