Skip to content
UpVoiced Docs

Integrations

Make

Make receives UpVoiced events through a custom webhook. A filter right after it recomputes the signature with Make’s own sha256 function, so later modules only see deliveries UpVoiced really sent.

Receive events

  1. In a new scenario, add Webhooks › Custom webhook and click Add. Under Advanced settings, set Get request headers to Yes and JSON pass-through to Yes: the signature covers the raw body, so the scenario needs the JSON as text, exactly as sent, plus the headers. Copy the webhook address.
  2. In UpVoiced, add a webhook endpoint with that address (owners and admins: Settings → Integrations → Webhooks, or POST /v1/webhooks) for the events this scenario handles. Copy the signing secret (whsec_…): it is shown once.
  3. Click Run once in Make, then make UpVoiced send something: Send test on the endpoint (or POST /v1/webhooks/{id}/test), or create a test post. Make learns the data structure from that request.
  4. Add Tools › Set variables after the webhook (module 2 below) with two variables, mapping from the webhook (module 1):
    • signature: {{get(map(1.headers; "value"; "name"; "x-upvoiced-signature"); 1)}}
    • timestamp: {{replace(first(split(get(map(1.headers; "value"; "name"; "x-upvoiced-signature"); 1); ",")); "t="; "")}}
    Request headers arrive as a list of name and value items; map picks the value named x-upvoiced-signature, and the timestamp is its first part after t=.
  5. Add JSON › Parse JSON (module 3) with the webhook’s body text as the JSON string, then the modules you want.
  6. Click the link between modules 2 and 3 and add a filter named “Signed by UpVoiced” with the two conditions below (both must pass). Then turn the scenario on.

The signature filter

Filter conditions
ConditionOperatorValue
{{2.signature}}Text operators: Contains (case sensitive)v1= followed by the sha256 function described below
{{abs(formatDate(now; "X") - 2.timestamp)}}Numeric operators: Less than or equal to300

Fill the three arguments of sha256(text; encoding; key):

sha256 arguments
ArgumentPut there
textThe timestamp variable, a dot (.) typed right after it, then the webhook’s body text (the JSON pass-through value), with no spaces in between.
encodinghex
keyYour whsec_… signing secret, the whole string. Anyone who can edit the scenario can read it.

With a key, Make’s sha256 returns the HMAC-SHA256 that UpVoiced puts after v1=. During a secret rotation the header carries one v1= per valid secret, so contains (not equals) is the right operator. Bundles that fail the filter stop there; Make has already answered UpVoiced with 200 Accepted, so they are not retried.

Check your formula

Before real deliveries arrive, evaluate your sha256 function once with these fictional values (for example in a temporary Set variables module). It must return the expected value exactly.

Signature test vector
keywhsec_FictionalDocsTestVector00000000000000000000
timestamp1792663200
body{"id":"evt_0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c21","type":"post.status_changed","occurred_at":"2026-10-22T12:00:00.000Z","workspace":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d","slug":"acme"},"data":{"post":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c13","slug":"export-to-csv-9c13","title":"Export to CSV","body_md":"Fictional request: export the whole board to CSV.","url":"https://acme.upvoiced.app/p/export-to-csv-9c13","board":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c10","slug":"feedback","name":"Feedback"},"status":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c12","name":"Shipped","category":"done"},"author":{"kind":"end_user","id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c14"},"vote_count":12,"comment_count":3,"merged_into_post_id":null,"deleted":false,"created_at":"2026-10-20T09:30:00.000Z"},"from_status":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c11","name":"Planned","category":"active"},"to_status":{"id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c12","name":"Shipped","category":"done"}},"actor":{"kind":"operator","id":"0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c15"}}
expectedfdc1624bf4399ef9ea41aea9e76ef542105d9da4b6b15dce437d1f41feb24c56

Recipes

After Parse JSON, map type, id (the event id, the same on every redelivery: use it as the unique key of what you create) and the fields under data. To handle several events in one scenario, add a Router with a filter on type per route.

New post: post.created

A post is created and is public at once (a post held for approval is not sent as post.created; its approval arrives as post.updated).

Useful items: data.post.title, data.post.url, data.post.board.name, data.post.status.name, data.post.vote_count. Add a row to a spreadsheet, create a card in your planning tool, notify a channel.

Status change: post.status_changed

A post moves to another status, for example from Planned to Shipped.

Useful items: data.post.title, data.post.url, data.from_status.name, data.to_status.name, data.post.vote_count. Update the linked record in your CRM, post a release note draft, alert the team.

Survey response: survey.response.created

A customer answers a Pulse survey (NPS, CSAT, CES and others) on a routing rule that sends responses to your endpoint.

Not subscribable yet. It arrives with Pulse survey routing rules, which send each matching response to a chosen endpoint.

Useful items: data.survey.slug, data.survey.type, data.response.score, data.response.band, data.response.text, data.response.followup.text. Open a follow-up task for detractors, log scores in a sheet, thank promoters.

Create posts from a scenario

Any module’s output (a form answer, a CRM record, a help-desk conversation) can become a post through POST /v1/posts. Add HTTP › Make a request:

Body fields
FieldValue
board_idRequired. The board’s id (list boards with GET /v1/boards).
titleRequired. Up to 140 characters.
body_mdOptional description in Markdown.
on_behalf_of_emailOptional. The customer becomes the author and a follower, and gets a “we logged your request” email.