# Incoming webhook

> How another system (CRM, form, ERP, n8n, Zapier) starts a community workflow by calling a Memberfy address. Creating, rotating and revoking the address, the token in the header, the HMAC signature with curl and Node examples, how the person is found, the trigger variables, the responses and the deliveries.

The **incoming webhook** is the reverse of [webhooks](/api/webhooks): instead of Memberfy notifying another system, another system (a CRM, a form, an ERP, an automation in n8n or Zapier) calls a Memberfy address and starts a [workflow](/notificacoes/workflows) for a person in the community. For example: the CRM closes a sale and the person receives the customer welcome series.

## Create the address

*Role: owner or admin.*

1. In **Administration › Workflows**, open the workflow and drag the **Webhook** trigger block (*Webhook received*) onto the canvas. Each workflow has a single trigger.
2. In **Who the person is**, say where the person is in the call's JSON: **By e-mail** or **By memberId**, and the **JSON path** (fields separated by dots, like `contact.email`, no list indexes).
3. In **Values to use in the steps**, if you like, map up to 20 values from the JSON (**Name** and path).
4. **Save** the workflow and, in the trigger panel, click **Create address**.

The window shows the **Address**, the **Token** and the **Signing secret (HMAC)** only once: *"Save them now: the token and the secret are shown only this once."* After that, the panel shows only the end of the token and of the secret.

- **Rotate** creates a new token and secret, which take effect at once; the system calling the current address stops working until it gets the new ones.
- **Revoke** turns the address off at once: calls start being refused.

## Call the address

Make a `POST` with a JSON body (`Content-Type: application/json`, up to 64 KB) to `https://api.memberfy.net/api/workflow-hooks`, with:

| Header | What it is |
|---|---|
| `X-Memberfy-Hook-Token` | The trigger's token. Always send it in the header: in the address (`/api/workflow-hooks/{token}`) it also works, but it ends up in logs |
| `X-Memberfy-Signature` | The signature, `t=<unix>,v1=<hex>` (see below) |
| `X-Memberfy-Delivery-Id` | A unique identifier for the call (up to 64 letters, digits, `-` and `_`). Repeated within 10 minutes, the call is refused |

### The signature (HMAC)

With **Require a signature (HMAC)** on (the default), only whoever holds the secret can start the workflow. The signature is mandatory when the trigger's path sends content (e-mail, message, comment, reaction or Slack): without it, the workflow does not turn on.

To sign:

1. `t` is the current time in seconds (Unix). It is valid for **5 minutes** either way.
2. Compute the HMAC-SHA256, in hexadecimal, with the secret, over `t`, a dot and the **raw body**, exactly the bytes that go in the call: `"<t>.<body>"`.
3. Send `X-Memberfy-Signature: t=<t>,v1=<hmac>`.

**curl:**

```bash
SECRET='the-trigger-secret'
TOKEN='the-trigger-token'
BODY='{"customer":{"email":"maria@memberfy.net"},"plan":{"name":"Anual"}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST https://api.memberfy.net/api/workflow-hooks \
  -H 'Content-Type: application/json' \
  -H "X-Memberfy-Hook-Token: $TOKEN" \
  -H "X-Memberfy-Signature: t=$T,v1=$SIG" \
  -H "X-Memberfy-Delivery-Id: crm-$(date +%s%N)" \
  --data "$BODY"
```

**Node:**

```js
const crypto = require('crypto');

async function startWorkflow(payload) {
  const body = JSON.stringify(payload); // sign exactly what goes in the call
  const t = Math.floor(Date.now() / 1000);
  const v1 = crypto
    .createHmac('sha256', process.env.MEMBERFY_HOOK_SECRET)
    .update(`${t}.${body}`)
    .digest('hex');

  const res = await fetch('https://api.memberfy.net/api/workflow-hooks', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Memberfy-Hook-Token': process.env.MEMBERFY_HOOK_TOKEN,
      'X-Memberfy-Signature': `t=${t},v1=${v1}`,
      'X-Memberfy-Delivery-Id': crypto.randomUUID(),
    },
    body,
  });
  return res.status; // 202 when accepted
}

startWorkflow({ customer: { email: 'maria@memberfy.net' }, plan: { name: 'Anual' } });
```

## The person and the variables

- Memberfy looks for the person **after** responding, by the e-mail or the `memberId` at the configured path. They must be a member of the community; otherwise, the delivery shows as *Person not found*.
- Only the mapped values are kept, in the person's run; the rest of the body is discarded.
- Each value becomes the variable `{{gatilho_<nome>}}`, for example `{{gatilho_plano}}`. It works in the text of messages, comments and Slack and **in the body** of e-mails, never in the subject or inside links.
- **How many times a person enters** works as in any trigger, and each person enters at most **5 times a day** through each workflow's webhook.
- A call that carries the `X-Memberfy-Run-Id` header of a run of the same workflow (an outside system echoing back the call the workflow made) is ignored, to avoid a loop. A workflow's Slack posts carry that header.

## Responses

| Status | When |
|---|---|
| `202` | Token and signature are valid. The response is the same whether or not the person is found; the result shows in the deliveries |
| `401` | Signature missing, wrong or outside the 5 minutes |
| `404` | Token unknown, revoked or from a deleted workflow |
| `409` | `X-Memberfy-Delivery-Id` repeated within 10 minutes |
| `413` | Body over 64 KB |
| `429` | More than 60 calls a minute or 5,000 a day on the same address; wait for the `Retry-After` |
| `503` | The repeat check is unavailable right now; try again |

## Deliveries

In the trigger panel, **Deliveries** lists each call received, with the result: **Started**, **Queued**, **Person not found**, **Workflow off**, **Already entered**, **Person’s daily limit**, **Unsigned** or **Repeated call (loop)**.

## Through the assistant (MCP)

The owner and the admin create the address (`create_workflow_webhook`: the token and the secret are shown once), rotate and revoke it (`rotate_workflow_webhook` and `revoke_workflow_webhook`, with confirmation); the team looks up the address (`get_workflow_webhook`, never the token) and the deliveries (`list_workflow_webhook_deliveries`). See [Tools · Community](/mcp/ferramentas-comunidade#workflows).

## Related

- [Workflows](/notificacoes/workflows)
- [Webhooks](/api/webhooks)
- [Zapier, Make and n8n](/integracoes/zapier-make-n8n)
