What is this about?
Webhooks at Alpheris work in both directions. Outbound is the part you set up yourself: you register a URL in the dashboard and we send the events you subscribed to there as a signed POST — covered right below. Inbound works the other way round and needs nothing from you: external platforms you've connected to your store report events back to Alpheris — for example a new Instagram DM, a changed Stripe subscription or an uninstalled Shopify app. Alpheris sets up these endpoints automatically when you connect the respective integration. As a rule, you don't need to register anything manually.
Outbound webhooks
With the Webhooks area in the dashboard you can register your own endpoints: Alpheris sends the events you subscribe to as a POST to your URL — signed, so you can verify the request really came from us. You'll find it in the left sidebar under Webhooks, right below API Keys. Available from Business upwards; owners and admins can manage it.
https. Then tick the events you want to receive.whsec_…xxxx. Lost it? The rotate icon generates a new one — the old one stops working immediately.ping event right away and shows you the real HTTP status your server returned. The dot in front of the name then turns green or red.Events
| Type | When |
|---|---|
conversation.created | A visitor starts a new conversation |
conversation.handoff_requested | A customer asks for a human agent |
conversation.closed | A conversation is closed |
message.received | A customer message arrives |
ticket.created | A ticket is created |
ticket.closed | A ticket is closed |
lead.created | A lead is captured |
Payload shape
{
"id": "evt_9f2c1a4b7e8d0c3a5b6f2e11",
"type": "ticket.created",
"created_at": 1785062400,
"data": { }
}Verifying the signature
Every delivery carries two headers. What gets signed is the raw body with the timestamp prepended and separated by a dot — that is <timestamp>.<body>, using HMAC-SHA256 with your signing secret, hex-encoded:
| Header | Contents |
|---|---|
X-Alpheris-Timestamp | Unix time of the delivery, included in the signature |
X-Alpheris-Signature | sha256=<hexdigest> |
User-Agent | Alpheris-Webhook/1 |
import hmac, hashlib
def signature_ok(raw_body: bytes, headers, secret: str) -> bool:
ts = headers["X-Alpheris-Timestamp"]
sig = headers["X-Alpheris-Signature"]
expected = "sha256=" + hmac.new(
secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
# compare in constant time, never with ==
return hmac.compare_digest(sig, expected)Good to know
| Topic | Behaviour |
|---|---|
| Protocol | https only. URLs carrying credentials (https://user:pass@…) are rejected. |
| Targets | Private, loopback and link-local addresses are blocked, including cloud metadata IPs. Checked at send time, not when saving. |
| Redirects | We do not follow redirects. Your URL must be the destination. |
| Timeout | 5 seconds per delivery. Answer quickly and process the payload asynchronously. |
| Success | Any 2xx status. Anything else counts as a failure and turns the dot in the overview red. |
| Retries | None for now. A failed delivery is not retried — plan your receiver accordingly. |
| Limit | Up to 10 endpoints per workspace. |
What else Alpheris calls outbound
Lead forms can additionally push their data straight to your API — setup and payload are documented under lead capture → form webhook. All other outbound calls go exclusively to the third-party services we depend on (email via Microsoft Graph, replies to Meta, billing via Stripe) and are not configurable webhooks.
Inbound webhooks at a glance
All inbound webhook routes verify the raw request body against a signature from the sender. If verification fails, the request is rejected — there is no unverified processing path.
| Platform | Endpoint | Signature header | Method |
|---|---|---|---|
| Shopify | POST /shopify/webhooks | X-Shopify-Hmac-Sha256 | HMAC-SHA256 (Base64) |
| Stripe | POST /stripe/webhook | Stripe-Signature | Stripe signature (construct_event) |
POST /integrations/instagram/callback | X-Hub-Signature-256 | HMAC-SHA256 (Meta) | |
POST /integrations/whatsapp/callback | X-Hub-Signature-256 | HMAC-SHA256 (Meta) |
Shopify webhooks
When you connect a Shopify store via OAuth, Alpheris registers the app webhooks automatically and receives them at POST /shopify/webhooks (alias: /api/integrations/shopify/webhooks). Among others, app uninstall, shop update and product delta sync are processed so that your catalog stays up to date in the knowledge base.
Authenticity is verified via the X-Shopify-Hmac-Sha256 header: Alpheris computes an HMAC-SHA256 over the raw body using the app secret and compares it in constant time (Base64) against the header.
# How Alpheris verifies the Shopify signature (simplified)
import hmac, hashlib, base64
digest = base64.b64encode(
hmac.new(app_secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
).decode("utf-8")
valid = hmac.compare_digest(digest, request.headers["X-Shopify-Hmac-Sha256"])Stripe webhooks
Stripe reports billing events to POST /stripe/webhook. From these, Alpheris keeps your plan and subscription status in sync — for instance when a subscription is created, updated or canceled, or an invoice is paid or fails.
Verification runs via the official Stripe library using the Stripe-Signature header and the webhook secret (construct_event). Invalid signatures are rejected with 400.
Idempotency
Stripe redelivers events on network or timeout errors. To ensure an event never takes effect twice (no double provisioning, no duplicate emails), Alpheris maintains an idempotency store: every event ID is atomically "claimed" before processing. Events already seen are skipped with ok (duplicate).
| Stripe event | Effect in Alpheris |
|---|---|
checkout.session.completed | Provision plan after completed checkout |
customer.subscription.created / .updated | Plan / status sync |
customer.subscription.deleted | Subscription ended → deactivate plan |
invoice.payment_succeeded / invoice.paid | Payment recorded |
invoice.payment_failed | Process failed payment |
Meta webhooks (Instagram & WhatsApp)
Instagram and WhatsApp are connected via Meta OAuth. Incoming messages and events reach Alpheris through the callback endpoints and land in your Inbox. Both use Meta's standard method: signature verification via X-Hub-Signature-256 (HMAC-SHA256 over the raw body using the app secret).
Verify handshake (WhatsApp)
When setting up the WhatsApp webhook, Meta performs a one-time verify handshake: a GET request with hub.challenge and hub.verify_token. Alpheris checks the verify token and echoes the challenge back once it matches. After that, only signed POST events are accepted.
# Incoming Meta event (simplified)
POST /integrations/instagram/callback
X-Hub-Signature-256: sha256=<hex-digest>
Content-Type: application/json
# Alpheris verifies sha256(raw_body, app_secret) against the header
# before the message is taken into the inbox.instagram_business_basic, instagram_business_manage_messages and instagram_business_manage_comments. Connecting is done entirely through the Dashboard — you don't have to enter a webhook URL by hand.Fetch your own events (polling)
While the events above are not emitted yet — and generally whenever you want the full set rather than individual events — use the API reference. Authenticate with an API key and poll the relevant endpoints on an interval.
GET /api/me and make sure the key is valid (otherwise 401/402).GET /api/inbox/threads or GET /api/inbox/tickets and bring new entries into your system.# Fetch new inbox threads (polling instead of an outbound webhook)
curl -sS https://console.alpheris.com/api/inbox/threads \
-H "Authorization: Bearer <SHOP_API_KEY>"Frequently asked questions
Do I have to enter a webhook URL somewhere?
No. The inbound webhooks are set up automatically when you connect the respective integration (Shopify, Stripe Checkout, Meta OAuth).
Why does a signature verification fail?
Almost always because a proxy or middleware modified the body, or a wrong/expired secret is involved. Since verification is done against the raw body, no reformatting may happen between receipt and verification. In that case, check the integration in the Dashboard and reconnect it if needed.
Can Alpheris notify my system on every new message?
Not via push at the moment. Instead, use polling via the API (see above).
