Skip to content
Docs
FA
Sign in
Start here
  • Overview
  • Quickstart
  • Concepts
Collect data
  • Designing events
  • Event dictionary
  • Placing events
  • Identity
  • Web SDK
  • Android SDK
  • Devices and push
  • Server to server
  • Product catalogue
  • Webhooks
Engage customers
  • Segments
  • Journeys
  • Transactional
  • Consent and caps
  • In-app and inbox
Analyze and export
  • Reports and exports
Developer reference
  • API reference
    • Ingest endpoints
    • Management API
  • Errors
  • Limits
  • OpenAPI
Developer tools
  • MCP server
  • Working with an agent
Privacy and changes
  • Personal data
  • Versioning

Inbound webhooks and other services

Taking events from a service that has no SDK, and the integrations that are ready.

#What an inbound webhook is

A way to take events from a service that does not have our SDK and is not going to get it.

A seller who only sells on Digikala has no site for an SDK to sit on. A shop running WooCommerce does, but installing a plugin and redeploying the site happens weeks after the decision. A webhook removes that gap: you paste a URL into that service's admin panel and the orders start arriving. The SDK becomes an upgrade later rather than a prerequisite.

There is no write key here. Your account is identified by the URL itself plus a signature, because there is nowhere in that platform's admin form to put a header.

#The address and the token

POST https://in.segmentic.net/v1/hooks/{source}/{token}

{source} is one of the seven listed in The sources, and {token} is a string the server mints when the connection is created.

The body is capped at one megabyte, exactly 1048576 bytes. A Shopify order with two hundred line items is about two hundred kilobytes, so this is generous and still small enough that a malicious POST cannot make us buffer a gigabyte.

The raw bytes are kept and the signature is verified against them before any parsing. This is the single most common webhook bug there is: decoding and re-encoding changes key order, whitespace and numeric formatting, and the signature then fails against a payload that was perfectly genuine.

The route is registered only when your installation has connections enabled.

#Creating a connection in the panel

INBOUND
WooCommerceOrders and products
ShopifyCommerce events
SegmentTracking events
SEGMENTICSegmentic event streamVerify inbound data and sign outbound relays
OUTBOUND
Outbound relaySigned HTTPS
ProfilesUpdated customer state
AutomationsTriggered journeys
The verified data loop between commerce systems, Segmentic profiles, automations and outbound relays

Three management routes, all on the control plane, which is the API the panel talks to. There is no connection management route on the management host.

The control plane is not routed from the internet. In the reference deployment, api.segmentic.net serves the management API on its own listener and the control plane's listener is deliberately not published. These three paths on https://api.segmentic.net fall to the catch-all and answer 404 unknown_endpoint. The only public path to them is the panel's own server-side proxy at https://app.segmentic.net/api/proxy/v1/..., which authenticates with the signed-in user's session cookie and answers 401 without one. An sk_seg_ key does not reach it, so creating a connection is something a person does on the panel's integrations screen.

Method and pathPermission
GET /v1/integrationssettings.read
PUT /v1/integrationssettings.write
POST /v1/integrations/{source}/enabledsettings.write
Creating or updating a connection
PUT /api/proxy/v1/integrations
Content-Type: application/json

{"source":"woocommerce","label":"فروشگاه اصلی","secret":"a-shared-secret"}
Response
{
  "id": 3,
  "source": "woocommerce",
  "label": "فروشگاه اصلی",
  "token": "yVJk3rQx7Pd1wNfZ0aLbCsTu",
  "has_secret": true,
  "enabled": true,
  "received": 0,
  "accepted": 0,
  "rejected": 0,
  "source_label": "ووکامرس",
  "webhook_url": "https://in.segmentic.net/v1/hooks/woocommerce/yVJk3rQx7Pd1wNfZ0aLbCsTu",
  "healthy": false
}

Things to note about that response:

  • secret is write-only and is never returned. Only has_secret says that a value is stored. That is a decision rather than a shortfall: the secret is sealed with the installation's key and no route hands it back, so a leaked management key does not carry your webhook secrets with it. If you leave secret empty on a later save, the stored one is left alone, so renaming the label does not require knowing the secret again.
  • token is minted by the server on the first save: twenty-four random bytes, base64url with no padding. Later saves do not rotate it, because that token is pasted into another platform's admin and changing it silently breaks a working connection the moment somebody merely renames it.
  • webhook_url is assembled server side so nobody has to build it from a token and a base and get it wrong. If the environment variable EMAIL_TRACK_BASE is not set on the API service, the field comes back as an empty string rather than being absent, and the panel has no URL to copy. A client that tests for the key being missing takes the wrong path; test the value.
  • healthy is enabled && last_error == "" && accepted > 0. So a brand-new connection that has not received anything yet reads as unhealthy. That is deliberate, not a bug.
  • source must be one of the seven known values, otherwise the answer is 400.

last_error is kept only for a failure, cleared on the next success, and truncated to 500 bytes. The field is omitempty, so a connection with no failure on record carries no last_error key at all, which is why the response above has none. The received, accepted and rejected counters are updated after the events are published, fire and forget.

Turning a connection off without deleting it:

Turning it off
POST /api/proxy/v1/integrations/woocommerce/enabled
Content-Type: application/json

{"enabled":false}

A disabled connection answers 404 to every delivery, exactly the answer an unknown token gets.

#Signature verification

The scheme is the same everywhere: base64(HMAC-SHA256(secret, rawBody)), compared in constant time against the trimmed header value. Constant time because anybody can call this endpoint, and a byte-at-a-time comparison leaks one character of the expected value every few thousand requests.

SourceSignature headerTopic header
shopifyX-Shopify-Hmac-Sha256X-Shopify-Topic
woocommerceX-WC-Webhook-SignatureX-WC-Webhook-Topic, falling back to X-WC-Webhook-Resource
segmentNone, see SegmentNone
digikala, basalam, torob, zarinpalNo header is read, see belowNone

If no secret is stored, every delivery gets 401. Segment is the only exception.

The WooCommerce fallback is for older plugins: when X-WC-Webhook-Topic is empty, the topic is built from X-WC-Webhook-Resource plus a dot plus X-WC-Webhook-Event (or updated if that is absent too). That is what a shop running a two-year-old plugin actually sends.

#The four Iranian sources whose signature cannot be verified over HTTP

Digikala, Basalam, Torob and ZarinPal do not work today over POST /v1/hooks/{source}/{token}. This is a known defect and it is written down here so nobody spends half a day looking for a misconfiguration that does not exist.

The mechanism: the function that decides which header to read the signature from returns the correct header for Shopify and for WooCommerce, and the empty string for every other source. No branch was written for these four. The signature verifier takes that empty string, compares a real base64 MAC against it, never matches, and the result is:

What actually comes back
{"status":"error","message":"signature mismatch"}

with status 401. At the same time the connection's last_error is set to bad signature and the rejected counter increments. If you have not configured a secret either, you get exactly the same 401, the same body and the same bad signature: the handler writes both constants for every verification failure whatever its cause. The reason no shared secret configured goes to the server log only, so no amount of reading the panel's last error tells the two apart.

Note that the transform library itself does support these four: its signature verifier accepts the same HMAC-SHA256 over the raw body for them, and the transform for all four is written and tested. What does not exist is a header being read on the HTTP side. Until that is added, contact us if you need one of these four sources.

#Responses

CaseStatusBody
Unknown source, unknown token, or a disabled connection404{"status":"error","message":"unknown webhook"}
The body could not be read400{"status":"error","message":"unreadable body"}
Signature did not match (any source but Segment)401{"status":"error","message":"signature mismatch"}
Segment credential did not match401{"status":"error","message":"unauthorized"}
The body could not be transformed200{"status":"ok"}
The transform produced no events200{"status":"ok"}
The bus and the disk buffer both failed503{"status":"error","message":"temporarily unavailable, please retry"}
Success200{"status":"ok","accepted":3}

Three things in that table are deliberate.

The 404 is the same for all three not-found cases, so the endpoint cannot be probed to discover which tokens exist.

A body that cannot be used gets 200 rather than an error. These platforms retry any non-2xx for days and that body will never change. The reason for the failure is recorded in the connection's last_error so the panel screen can say why nothing arrived.

accepted is absent from the response when it is zero, because the field is omitempty. So {"status":"ok"} means zero events.

Webhook events are metered and billed, one per accepted non-duplicate event. Unlike on-site interactions, which are not counted at all. There is no quota gate on this path.

#Deduplication

Every transform derives a deterministic message_id from the source platform's own identifier. All of these platforms retry, several of them aggressively, and without it a customer's revenue is counted twice the first time their network blinks, on the number they look at most.

SourceIdentifier shape
Shopify ordershopify:<event>:<order id>
Shopify customershopify:identify:<customer id>
WooCommercewoocommerce:<event>:<order id>:<status>
SegmentIts own messageId, and a derived one if that is absent
The four Iranian sources<source>:<event>:<key>, where key is the first present of order_id, ref_id or click_id, falling back to the user id

The order status is inside the WooCommerce identifier so that an order moving through three states is three events, while a retry of any one of them is still caught.

An identifier of "" or "0" produces an empty message_id, meaning that event does not benefit from duplicate detection.

Duplicates are not counted in accepted and are not an error: a retry after a timeout is the ordinary case, not an anomaly.

#The sources

Seven sources, in the order they appear in the panel. The four Iranian platforms lead deliberately: a picker that starts with Shopify, which an Iranian merchant cannot use at all, reads as a product built for somewhere else.

#Digikala

The seller-panel order webhook. A shop that only sells on Digikala has no site for an SDK, so this is its only path to order data.

Accepted topics: order.created, order.confirmed and an empty topic map to order_completed; order.cancelled and order.canceled to order_cancelled; order.returned and order.refunded to order_refunded. Anything else is skipped and the answer is 200.

A body that is accepted
{
  "order_id": 88213445,
  "status": "confirmed",
  "created_at": "2026-08-06 11:42:00",
  "total_price": 2450000,
  "customer": { "id": 5512, "mobile": "09123456789", "name": "علی" },
  "items": [
    { "product_id": 771, "title": "کفش رانینگ", "quantity": 1, "price": 2450000 }
  ]
}

Properties on the resulting event: order_id, source, currency which is always IRR and is never converted, revenue, item_count which here is a number, and product_id and product_name from the first line.

#Basalam

The handmade and local-goods marketplace. Topics: order.paid, order.created and an empty topic map to order_completed; order.cancelled and order.canceled to order_cancelled.

A body that is accepted
{
  "id": 90112,
  "status": "paid",
  "created_at": "2026-08-06T11:42:00",
  "amount": 780000,
  "customer": { "id": 341, "mobile": "09123456789", "username": "ali_b" },
  "product": { "id": 4471, "title": "شمع دست‌ساز" }
}

Properties: order_id, source, currency of IRR, revenue, and product_id and product_name when present.

#Torob

The price comparison engine. It sends clicks rather than orders, which makes it the top of the funnel for a shop whose traffic arrives through comparison.

Topics: empty, click or referral.

A body that is accepted
{
  "click_id": "trb_88a1",
  "product_id": 771,
  "title": "کفش رانینگ",
  "price": 2450000,
  "user_id": "trb_u_5512",
  "mobile": "09123456789",
  "created_at": "2026-08-06 11:42:00"
}

The event produced is always product_viewed and is never an order. Reporting a referral as a purchase inflates every conversion number a comparison-driven shop looks at. Properties: source, click_id, and product_id, product_name and price when present.

#ZarinPal

A payment gateway callback. Worth having even for a shop that already sends order_completed: the gateway is the only party that knows a payment actually settled, and "order placed" and "money received" differ by the abandonment rate, which in Iranian e-commerce is not small.

Topics: empty, payment or verify.

A body that is accepted
{
  "authority": "A00000000000000000000000000123456789",
  "ref_id": 77112233,
  "amount": 245000,
  "status": "OK",
  "email": "ali@example.ir",
  "mobile": "09123456789",
  "order_id": "ORD-9",
  "created_at": "2026-08-06 11:42:00"
}

Only a settled payment produces an event. After trimming and upper-casing, status must be empty, OK, 100 or SUCCESS. Anything else is skipped as payment not settled: <status> and the answer is 200. ZarinPal reports the failures too, and recording a failed payment as an order is exactly how a revenue report ends up above what the gateway settled.

order_id may be a string or a number, because a merchant's order id is whatever their own checkout produces: "ORD-9" for one shop and 55123 for the next. If the structure accepted only one of the two, the whole body would fail to decode and a settled payment would be lost.

amount is in toman on ZarinPal's v4 API. We record both: revenue is amount * 10 in rial, and amount_toman is the raw amount. A report that mixes the two is off by a factor of ten and looks plausible either way. The other properties: source, ref_id, authority, currency of IRR, and order_id when its value is neither empty nor 0.

Identity for all four Iranian sources is picked in this order: phone first, normalised to E.164, then email lower-cased, then the platform's own internal id. The phone number is the identifier an Iranian shop's own systems key on and is most likely to match a profile already here. name becomes the first_name trait. With no identity at all the whole body is skipped as no identity and the answer is still 200.

Timestamps for these four are parsed in Tehran, not in UTC. These platforms report local time with no offset, and reading it as UTC puts every order three and a half hours early, landing a morning order in the previous day's report. The accepted formats are RFC3339, 2006-01-02T15:04:05, 2006-01-02 15:04:05 and 2006-01-02. An unreadable timestamp falls back to now rather than rejecting the event: an event with a slightly wrong timestamp is worth far more than one that never arrived.

#WooCommerce

Only two topics are read: order.created and order.updated.

The order status decides the event, not the topic. WooCommerce sends one webhook per status change, and reading the topic alone turns an order that moves from pending to processing to completed into three purchases.

statusEvent
processing, completedorder_completed
cancelled, failedorder_cancelled
refundedorder_refunded
pending, on-holdcheckout_started
Anything elseSkipped as order.<status>

Identity: billing.email then billing.phone. With neither, the body names nobody. Persian digits inside a phone number are folded to ASCII. billing.city becomes the city trait. date_created_gmt is a timestamp with no zone and is read as such.

Properties: order_id, order_number (falling back to order_id), status, source, and when there are line items product_id, product_name and item_count, which here is a string. When total is above zero, revenue and currency are added, with IRR as the default currency.

#Shopify

TopicEvent
orders/create, orders/paidorder_completed
orders/cancelledorder_cancelled
refunds/createorder_refunded
checkouts/create, checkouts/updatecheckout_started
customers/create, customers/updateAn identify
Anything elseSkipped, answer 200

Identity order: email first, then phone, then the platform's own id. Email is what the customer's other systems key on; the platform id is stable but only means anything inside that platform, so putting it first makes the same person two profiles the day they also arrive through the SDK. A guest checkout with no email and no phone produces no event and the answer is 200.

Properties: order_id, order_number, source, product_id (the first line's SKU, falling back to its product id), product_name, item_count which is a string, product_ids which is the SKUs joined by commas, checkout_url when the body carries abandoned_checkout_url, and, when the amount is positive, revenue and currency defaulting to IRR.

An order with three line items produces one order_completed event, not three. A per-line event would triple every revenue number.

#Segment

Segment does not sign. Instead the Authorization header is compared against the stored secret in constant time, an optional Bearer prefix is tolerated, and the check runs only when a secret is configured, so a customer who has not set one is not locked out of their own connection.

Both a single object and the {"batch":[...]} form are accepted, and the batch form is what Segment actually sends at volume.

typeResult
trackA track event with the same event
identifyAn identify
pageA page with event set to name or page
screenA screen with event set to name or screen
Anything elseThat item is dropped

A body with neither userId nor anonymousId is dropped. One bad item does not lose the batch: Segment sends hundreds at a time, and rejecting the whole batch for one malformed row costs the customer the other ninety-nine. Segment's own messageId is kept as the deduplication key, so a replay reaching us through both paths is one event rather than two.

Sending an event through a Segment connection
curl -s -X POST https://in.segmentic.net/v1/hooks/segment/yVJk3rQx7Pd1wNfZ0aLbCsTu \
  -H "Authorization: Bearer a-shared-secret" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "track",
    "event": "order_completed",
    "userId": "u_9137",
    "messageId": "seg_0f21",
    "timestamp": "2026-08-06T11:42:00Z",
    "properties": { "revenue": 2450000, "currency": "IRR" }
  }'
Response
{"status":"ok","accepted":1}

#Event relays: data going out

The outbound twin of the inbound webhook. A relay POSTs events to your address as they arrive, so your CRM or fulfilment system does not have to poll our API every thirty seconds.

These five routes are on the control plane too, alongside the connection routes above, and there is no relay route on the management host.

The control plane is not routed from the internet. These five paths on https://api.segmentic.net fall to the catch-all and answer 404 unknown_endpoint. The only public path to them is the panel's own server-side proxy at https://app.segmentic.net/api/proxy/v1/..., on the signed-in user's session cookie. An sk_seg_ key does not reach it, so a relay is created and retried on the panel's relay screen. What a relay then does, delivering to your endpoint, needs no API of ours at all.

Method and pathPermission
GET /v1/relayssettings.read
PUT /v1/relayssettings.write
DELETE /v1/relays/{id}settings.write
GET /v1/relays/{id}/deliveriessettings.read
POST /v1/relays/{id}/retrysettings.write
Creating a relay
PUT /api/proxy/v1/relays
Content-Type: application/json

{
  "name": "انبار",
  "url": "https://ops.example.ir/hooks/segmentic",
  "events": ["order_completed"],
  "filters": [{ "prop": "revenue", "op": "gte", "value": "5000000" }],
  "enabled": true,
  "secret": "a-relay-secret"
}
Response
{"id":4}

An empty events list means every event. The default is deliberately the noisy one, because the alternative, empty meaning nothing, creates a relay that looks configured and silently does nothing, which takes a support ticket to discover.

Filters are conditions on the event's properties, shaped {"prop","op","value"}. The operators are eq, ne, contains, prefix, gt, gte, lt, lte, exists, missing. Eight filters at most, because past that the rule is a segment definition, and a relay is a firehose with a tap rather than a query engine. Every filter must match, not any of them. A comparison against a property the event does not carry is false, not an error. A number that arrived as text still compares as a number.

Validation failures get 400: an empty name, an empty URL, an empty event name, an unknown operator, too many filters. If the installation has no sealing key the answer is 503. The audit line records the destination and never the secret.

The body your endpoint receives is deliberately small and stable, and is not our internal structure. The receiving system is somebody else's code that we do not get to redeploy, so every field here is a promise:

What your endpoint receives
{
  "event": "order_completed",
  "user_id": "u_9137",
  "anonymous_id": "a_9f21c4",
  "timestamp": "2026-08-02T12:00:00Z",
  "properties": { "revenue": 2500000, "city": "تهران" },
  "message_id": "shopify:order_completed:450789469"
}

user_id, anonymous_id and properties are absent when empty. The two property maps we keep separate internally are merged back into one here; handing you two maps to reassemble would be our storage layout leaking into your code.

Fan-out runs after the events are durable, never before. A relay fired for an event that then failed to write would tell the customer's fulfilment system about an order this platform has no record of.

#Relay signing

If you have stored a secret, every request carries these two headers:

Signature headers
X-Segmentic-Timestamp: 1786032120
X-Segmentic-Signature: 4f1c9a2b...

The signature is hex(HMAC-SHA256(secret, timestamp + "." + payload)). The timestamp is inside what gets signed, so a captured body cannot be replayed against your endpoint a week later and still verify.

The other headers: Content-Type: application/json and User-Agent: Segmentic-Relay/1.

#Retries

One delivery times out after fifteen seconds. Ten attempts at most, backing off exponentially from ten seconds and capped at an hour, spanning a little under two hours in total. The cap matters more than the curve: without it the tenth attempt would be two days out, and a delivery that arrives two days late is worse than one that never arrives, because the customer's system has already reconciled without it.

  • 2xx succeeds.
  • 408, 429 and any 5xx are retried: the endpoint is up and having a bad time.
  • Every other 4xx is permanent. Retrying a 401 ten times does not make the credential valid, and it does make the customer's error log ten times noisier.
  • A URL that will not parse, and an address the SSRF guard refuses, are permanent too. The guard runs before the request and again at every redirect hop, because a public hostname that redirects to 127.0.0.1 defeats a check done only on the first URL.

The first 500 bytes of a failure body are kept for the delivery log screen, because an endpoint that answers 400 usually says why in the body, and that sentence is the whole value of the screen.

POST /v1/relays/{id}/retry puts every dead delivery back in the queue and returns {"requeued": N}. An endpoint misconfigured for an hour leaves a pile of dead deliveries, and the alternative is asking the customer to replay the events from their own side, which they cannot, because the events were ours.

#Short links

For one channel and one reason: an SMS is billed by the part, a Persian part is seventy characters, and a campaign URL routinely takes sixty of them. The link is often the single most expensive thing in the message, and a sentence one word too long doubles the bill for the whole audience.

The path is GET /s/{code}. The prefix is deliberately one character: it is in every SMS the shortener rewrites, and each character in the prefix is one fewer available to the message.

code is seven characters from a Crockford-style base32 alphabet: 0123456789ABCDEFGHJKMNPQRSTVWXYZ. The letters I, L, O and U are deliberately absent. A short link is read aloud, typed from a screenshot and dictated over the phone, and every one of those four is a support ticket that begins "it says page not found".

The code is derived, not issued:

The code formula
code = base32(sha256(tenant_id || 0x00 || target))[:7]

Two consequences of that design are spelled out in the code. The same URL in the same account always shortens to the same code, so the table holds one row per link rather than one per send, and a campaign re-rendered after a crash produces byte-identical messages. And the account id is inside the hash, so two accounts shortening the same URL get different codes and neither can discover the other's by guessing.

The rewrite is applied only on the SMS path, and even there:

  • Never on a service line. There the text must match a pattern registered with the operator, and rewriting any part of it makes the gateway reject the send.
  • The code-to-target mapping is stored before the message goes out, and a failure to store it leaves the long link in place. A code printed in somebody's inbox that resolves to nothing is worse than a link that costs an extra part.
  • Only http and https. A deep link like myapp://order/12345 is parsed by the app itself and a web redirect breaks the routing it exists to drive.
  • And only when the result is actually shorter. For a customer whose own domain is already short, the rewrite would make the message longer.

Storing the mapping is ON CONFLICT DO NOTHING and deliberately does not update the target: letting a later send repoint an existing code would silently change where a link already sitting in somebody's inbox goes.

Setup: set SHORT_LINK_BASE and point that domain at the collector, because /s/{code} is served there. Until it is set the shortener is off and messages go out exactly as they do today.

#What a click records

The redirect is 302 and never 301. A permanent redirect is cached by the handset and every follow after the first never reaches us, which turns the click count into a count of first-time followers without anything saying so.

The order of work: one primary-key read, then the redirect, and the click is counted after the response is written, in a goroutine on a context detached from the request with a five second timeout. The request's own context is cancelled the moment the response is written, and the update would be rolled back almost every time. A failure to count is logged at warn only.

An unknown code, and a lookup error too, get a plain 404 rather than an error page. An unknown code is almost always a mistyped one, and there is nothing useful to say to somebody who read seven characters off a screenshot.

What is recorded is exactly this and no more:

Everything that is written
UPDATE short_links SET clicks = clicks + 1, last_click = $2 WHERE code = $1

A counter and a last-click timestamp, on the link's own row. No person, no IP, no user agent.

There is no token here and there cannot be one. Every other tracking endpoint is reached with sg_mid and a signature and is refused without them, because it is reached by software we gave the link to. This one is reached by a phone carrying nothing but the seven characters printed in the message.

So the attribution model here is per link and per campaign, never per person. Sixty thousand recipients of one campaign share one code. For push and email the per-person model is still in place and is better: sg_mid and sg_t are stamped onto the link, the person lands on the customer's own site, and the SDK already there reports the arrival. For email there is no click redirect endpoint at all, because a hop of ours could fail, would add a domain that can be blocklisted, and, worst, would count every click a corporate mail scanner makes while checking the message, which on a business list can be most of them.

No endpoint and no screen shows you this counter. The function that reads a campaign's clicks exists in the store layer and has no caller anywhere in the tree; no API route reads it and no panel screen displays it. The clicks are counted into a column that cannot be read today.

#What is not there today

Only things that change your code belong here: something you would look for and not find, or something you have to do another way. Deliberate decisions, such as the absent email click redirect and the secret that never comes back, are explained where the decision is made.

  • Signature verification for Digikala, Basalam, Torob and ZarinPal over HTTP. See The four Iranian sources.
  • Reading short-link clicks. The counter exists and there is no way to see it.
  • Rotating a webhook token. Saving again does not change it, and there is no other route that changes it. If a token leaks, disable the connection and contact us.
  • The connection and relay management routes on the management host. They are registered on the dashboard's control plane only, and that listener is not routed from the internet, so an sk_seg_ key reaches none of them. The panel's own proxy is the way in.
PreviousWorking with an agentNextPersonal data

On this page

  • What an inbound webhook is
  • The address and the token
  • Signature verification
  • Responses
  • Deduplication
  • The sources
  • Event relays: data going out
  • Short links
  • What is not there today

Segmentic

This page is written from the code