Webhooks and API

Inbound Webhooks

Receive HTTP POSTs from external systems and start flows

Inbound webhooks let external systems push data into Wexio. Any service that can POST JSON, such as a payment provider, your own backend or a no-code tool, can start a Wexio flow by calling a unique URL.

Inbound webhook detail page

Request Flow

External System                        Wexio
      │                                   │
      │  POST /webhooks/in/{connectionId} │
      │  Auth header (mode-specific)      │
      │  Body: { ...JSON payload... }     │
      │ ────────────────────────────────► │
      │                                   │
      │                         1. Verify authentication
      │                         2. Check payload size (≤100 KB)
      │                         3. Parse JSON
      │                         4. (Optional) Capture schema
      │                         5. Resolve the target contact
      │                         6. Find matching flow triggers
      │                         7. Start each matching flow
      │                                   │
      │  202 Accepted                     │
      │ ◄──────────────────────────────── │

Creating an Inbound Connection

From Settings → Webhooks and API → Webhooks, click New connection and set Direction to Inbound.

Create inbound connection modal

Fill in the form:

FieldWhat to enter
Connection nameThe system on the other side, for example "Stripe Payments" or "n8n Lead Pipeline"
Contact identifier typeChat ID, Phone number, Email or Channel user ID. See Contact Resolution
JSON path to identifierWhere the identifier sits in the payload, for example $.customer.phone
Channel integrationNeeded only for Channel user ID
Authentication modeThe mode your sender uses, or click a provider chip. See Authentication Modes

Click Create connection. The toast says Webhook connection created. Wexio generates:

  • A unique URL: https://api.wexio.io/webhooks/in/{connectionId}
  • A signing secret (for Shared Secret mode)

The next screen shows the Endpoint URL and the Webhook secret, each with a copy button, and the Capture schema and Inject payload buttons.

Copy the secret now. We won't show it again - you'll have to rotate it if lost. If you lose it, open the ... menu on the connection page and click Rotate secret to generate a new x-webhook-secret (see Rotate the Signing Secret).

Your plan caps how many connections you can create. See Connection Limit.

The Connection Page

Click Open on a connection in the list. The header has these buttons:

ButtonWhat it does
Inbound endpointShows the Endpoint URL, the x-webhook-secret header, the optional x-idempotency-key header and a cURL example you can copy
LogsOpens the request log
Pause / ResumeStops or restarts accepting requests
...Rotate secret and Delete

Below the header the page has the sections Authentication, Contact resolution, Payload schema, Ingest a sample payload and Used by. The connection page never shows the secret again: The current secret isn't displayed again after creation. Rotate the secret to view a new value.

Inbound connection page with the Inbound endpoint popover open showing the URL, the secret header and a cURL example

Authentication Modes

Pick the auth mode your partner system uses. Set one in Authentication mode, or click a chip under Provider presets to set it for you. Later, change it with Change in the Authentication section.

Authentication mode picker
ModeWhen to useSecret format
Shared secret (default)Any system that lets you configure a custom request header - n8n, Zapier, Make, IFTTT, your own backendRandom string generated by Wexio
StripeStripe webhooks (Payments, Billing, Connect)whsec_… from Stripe dashboard
SlackSlack Events API, slash commands, interactive components"Signing Secret" from the app's Basic Information page
Svix (Clerk, Resend, Orb, Knock, Dub, …)Svix-powered senders, including Clerk and Resendwhsec_… from the provider's dashboard
Paddle Billing v2Paddle Billing v2 notificationspdl_ntfset_… from Paddle's Notifications
Revolut BusinessRevolut Business webhooksShown once on webhook creation in Revolut
HubSpot (signature v3)HubSpot webhook subscriptionsYour HubSpot app's client secret
MailgunMailgun webhooksHTTP webhook signing key from Mailgun
ZendeskZendesk webhooksSigning secret of the Zendesk webhook
PaystackPaystack webhooksYour Paystack secret key
VercelVercel webhooksSecret shown once when you create the Vercel webhook
GitLab (secret token)GitLab webhooksA secret token you choose, the same in GitLab and Wexio
Generic HMAC SHA-256GitHub, Bitbucket, Shopify, Razorpay, PagerDuty, Linear, Dub, any custom HMAC that signs the raw body with SHA-256Provider-specific; you configure the header name and encoding

Shared Secret

Send the secret as the x-webhook-secret request header on every POST:

POST /webhooks/in/{connectionId}
x-webhook-secret: <your-secret>
Content-Type: application/json

{ ...payload... }

Wexio bcrypt-compares the header against the stored hash. There is no per-body signature, so use this mode when HTTPS is your only transport guarantee or when you handle replays with X-Idempotency-Key.

Provider Presets

The New connection modal has a Provider presets section. A chip sets Authentication mode and, for HMAC presets, the header fields. You still paste the provider's signing secret.

Provider presets in the New webhook connection modal, with HubSpot, Mailgun, Zendesk, Paystack, Vercel and GitLab under Dedicated providers
GroupChips
Dedicated providersStripe, Paddle Billing, Revolut, Slack, Clerk, Resend, HubSpot, Mailgun, Zendesk, Paystack, Vercel, GitLab
HMAC presetsGitHub, Bitbucket, Shopify, Razorpay, PagerDuty, Linear, Dub
Not yet supported - use proxyNetlify, Tipalti, Orb, Knock, Humanloop, Intercom, Discord, SendGrid, PayPal, Wise, Paddle Classic, Apple IAP, Google Play Billing, Payoneer

The last group cannot be verified yet. Send those providers through a proxy, as described in Not Yet Supported Natively.

Provider Modes

Pick the mode and paste the signing secret from the provider's dashboard. Wexio handles header parsing, the signature check and timing-safe comparison. Modes that sign a timestamp (Stripe, Slack, Svix, Paddle, Revolut, HubSpot, Mailgun, Zendesk) reject requests older than 5 minutes.

ModeNotes
HubSpot (signature v3)Checks X-HubSpot-Signature-v3 against the URL HubSpot called. The optional Public URL (optional) field takes the exact https URL you registered in HubSpot. Leave it empty to use this endpoint's address
MailgunThe signature sits in the request body, not in a header. Each signature token is accepted once per connection
ZendeskChecks X-Zendesk-Webhook-Signature with its timestamp header
PaystackChecks x-paystack-signature (HMAC-SHA512 of the body). No timestamp, so no replay protection
VercelChecks x-vercel-signature (HMAC-SHA1 of the body). No timestamp, so no replay protection
GitLab (secret token)Compares X-Gitlab-Token with your token. GitLab does not sign the body, so there is no replay protection

Wexio accepts several signatures in one header during a secret rotation, for Svix, Revolut and PagerDuty (Generic HMAC SHA-256 reads them too).

Change authentication modal with provider presets, Stripe selected as the authentication mode and an empty Signing secret field

Generic HMAC SHA-256

Use this mode for any provider that signs the raw body with HMAC-SHA256. It has no replay protection, because the signature carries no timestamp. Providers whose signature covers more than the body, or uses another hash, do not verify here. See Custom HMAC for the full setup.

Change authentication modal with Generic HMAC SHA-256 selected, showing signing secret, signature header name, encoding and header prefix fields
FieldDescription
Signing secretThe provider's secret
Signature header nameWhere the signature lands (for example X-Hub-Signature-256 for GitHub, X-Shopify-Hmac-Sha256 for Shopify)
EncodingHex (most providers) or Base64 (Shopify)
Header prefix (optional)String to strip before comparing (GitHub prefixes with sha256=)

The HMAC presets chips fill in these fields for GitHub, Bitbucket, Shopify, Razorpay, PagerDuty, Linear and Dub.

Not Yet Supported Natively

Wexio cannot verify these providers' signatures yet: Netlify, Tipalti, Orb, Knock, Humanloop, Intercom, Discord, SendGrid, PayPal, Wise, Paddle Classic, Apple IAP, Google Play Billing and Payoneer.

Send their events through n8n or Zapier. The proxy receives the event and forwards it to Wexio with the x-webhook-secret header, so the connection uses Shared secret (default).

See Integrations for the full list and per-provider setup steps.

Contact Resolution

Wexio maps each incoming payload to a contact in your organisation using these settings.

Contact identifier type list with Chat ID, Phone number, Email and Channel user ID
FieldDescription
Connection nameDisplay name - only affects the UI
Identifier type (Contact identifier type in the create form)One of Channel user ID, Phone number, Email, Chat ID
JSON path to identifierWhere to read the identifier from the payload - JSONPath syntax, e.g. $.customer.email
Channel integrationRequired when identifier type is Channel user ID - scopes the lookup to a specific Telegram bot / WhatsApp number / Instagram page

Examples:

PayloadIdentifier typeJSON pathChannel integration
Stripe CustomerEmail$.data.object.email-
Telegram user eventChannel user ID$.user.idyour Telegram bot
WhatsApp callbackPhone number$.contacts[0].wa_idyour WhatsApp number
Internal systemChat ID$.wexio_chat_id-

If the path returns nothing, or the identifier doesn't match a contact that has a chat, the log is tagged IDENTIFIER_NOT_FOUND and no flow runs. Wexio never creates a contact from a webhook.

Capture Schema

Wexio learns the shape of your payload from a sample, so the fields become flow variables. This step also tests that the partner reaches the URL.

Connection page with Stop capture and Inject payload buttons and the message Waiting for payload

To capture a schema:

  1. Tap Capture schema on the connection detail page. A 5-minute tunnel opens.
  2. Trigger a real event on the partner side (or use Inject payload to paste a sample JSON manually).
  3. Wexio infers the schema from that single payload - every scalar leaf becomes a variable - replaces the current schema (no merge), bumps the version, and closes the tunnel.
  4. The payload used for capture is not dispatched to flows - capture is a separate path so you can experiment safely.

The Payload Schema panel shows every learned field, its inferred type and whether it is required. Use these fields as {{webhook.<path>}} variables in any flow bound to this connection.

Payload schema with learned fields

Schema is advisory - Wexio never rejects a payload because a field is missing. The schema only drives the variable picker. Missing fields render as null at runtime.

Inject a Sample Payload

If you cannot trigger the partner on demand, use Inject payload to paste JSON directly. Wexio treats it like a live capture.

Inject payload dialog

Bind the Connection to a Flow

Open any flow, add a Webhook Received trigger (Pro and Enterprise plans), and pick this connection. The flow must be published. It runs each time a matching payload arrives and a contact resolves.

One connection can serve many flows. The connection owns the auth and mapping, and each flow decides what to do with the payload.

Request Headers

HeaderPurpose
Content-Type: application/jsonRequired
x-webhook-secretRequired when auth mode is Shared secret (default)
Stripe-Signature / X-Slack-Signature / svix-signature / Paddle-Signature / Revolut-Signature / X-HubSpot-Signature-v3 / X-Zendesk-Webhook-Signature / x-paystack-signature / x-vercel-signature / X-Gitlab-TokenRead automatically by the matching provider mode (Mailgun reads its signature from the body)
x-request-idOptional correlation ID; echoed in the history log
X-Idempotency-KeyOptional dedup key - replays within 24 hours return the cached response

Outcomes & HTTP Responses

Every request lands a row in the history log with one of these outcomes:

OutcomeStatusMeaning
ACCEPTED202Authenticated, parsed, at least one flow dispatched
SAMPLE_CAPTURED202Absorbed by an open capture tunnel; no flow ran
UNAUTHORIZED401Signature / secret mismatch
INVALID_JSON400Body was not valid JSON
PAYLOAD_TOO_LARGE413Body exceeded 100 KB
IDENTIFIER_NOT_FOUND400No contact resolved from the payload
TRIGGER_NOT_FOUND400Connection is paused, deleted, or has no bound flow triggers. The errorCode is WEBHOOK_UNAVAILABLE when the plan no longer includes webhooks
NEWS_INGEST_FAILED400A News widget bound to the connection rejected the payload (for example a missing items array). Flow triggers on the connection do not run for that payload
ERROR400Unclassified failure - file a ticket if this appears

The endpoint answers only 202, 401, 413 or 400. The errorCode field in the JSON body tells the 400 cases apart.

Rotate the Signing Secret

Settings → Webhooks and API → Webhooks → your connection, then the ... menu in the header and Rotate secret. The menu item exists on inbound connections only. Confirm in Rotate webhook secret? with Rotate secret. The dialog warns that the current secret stops working immediately and that external systems using it get 401 Unauthorized. Rotation generates a new x-webhook-secret for Shared secret mode and shows it once as New secret (Copy it now - we don't show secrets again.), then the toast says Secret rotated. Update your external systems. The connection page never shows it again. The old one stops working immediately, so update the partner configuration first or accept a short window of 401s.

Stripe, Slack, Paddle, Revolut, PagerDuty and several other providers support dual-signing during rotation. Use that window on their side and paste the new secret into Wexio last, with Change under Authentication.

Pause & Resume

To stop accepting requests without deleting the connection, click Pause on the connection detail page. A paused connection returns 400 with errorCode: TRIGGER_NOT_FOUND to every request. You can resume at any time.

Change Authentication

In the Authentication section, click Change to open Change authentication. Pick the Authentication mode (or a provider chip), then paste the Signing secret from your provider. For Generic HMAC SHA-256, also set the Signature header name, Encoding and the optional Header prefix. The stored secret is never shown again: leave the field empty to keep it, or paste a new value to replace it. Click Save changes; a successful change shows Authentication updated.

When you create a connection in a provider mode, the Signing secret is required: without it you see Paste the signing secret from your provider.

Delete a Connection

Open the ... menu and click Delete, then Delete connection in Delete webhook connection?. The sender starts receiving 400 with errorCode: TRIGGER_NOT_FOUND. If flows use the connection, the dialog warns that their webhook triggers stop firing. The toast says Webhook connection deleted.

Rules and Caveats

  • Payload limit: 100 KB. Larger payloads are rejected with 413.
  • Credentials are never returned. The API does not expose the stored signing secret.
  • Signature headers are not logged. x-webhook-secret, Stripe-Signature, Authorization, and similar are scrubbed on the server before the history log is written.
  • Retention: 7 days of history per connection, including auth failures for self-diagnosis.

Was this page helpful?

On this page