{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-PublicAPI/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Overview","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":0},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["StreamAPI webhooks push real-time ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["data change events"]}," to your application when entities (e.g., operatories, patients, appointments) are created, updated, or deleted. Notifications are delivered over ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Amazon SNS (Simple Notification Service)"]},". Your server receives an HTTP POST for each event and must handle both ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["subscription confirmation"]}," and ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["notification"]}," payloads."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This guide explains how to create and set up webhooks specifically for the StreamAPI: registering your endpoint, understanding the payload structure, confirming subscriptions, and verifying message signatures."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":1},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["StreamAPI access"]}," – Your application must be authorized to use the StreamAPI (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/publicapi/obtaining-prod-access"},"children":["Obtaining Production Access"]}," if applicable)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Public endpoint"]}," – Your webhook URL must be reachable from the internet over ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["HTTPS"]},". AWS SNS cannot deliver to private IPs or localhost unless you use a tunnel (e.g., ngrok) for development."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["HTTP server"]}," – A server that accepts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," requests and can respond quickly (e.g., return 2xx within a few seconds) so SNS does not retry unnecessarily."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"creating-and-registering-your-webhook","__idx":2},"children":["Creating and Registering Your Webhook"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-expose-a-webhook-endpoint","__idx":3},"children":["1. Expose a webhook endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your application must expose an HTTP endpoint that:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Accepts ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["POST"]}," requests."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Accepts a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["JSON"]}," or ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["text/plain"]}," body (SNS may send JSON as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["text/plain"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Returns a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["2xx"]}," status code after processing (or immediately after receiving) the request so SNS considers the delivery successful."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Example URL form:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"https://your-domain.com/webhook\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For local development, use a tunnel (e.g., ngrok) so SNS can reach your machine:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"https://abc123.ngrok.io/webhook\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-register-the-endpoint-with-the-platform","__idx":4},"children":["2. Register the endpoint with the platform"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Register your webhook URL with the StreamAPI by submitting a support ticket."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Provide the full webhook URL (HTTPS)."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After registration, the platform creates an SNS subscription for your endpoint."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-confirm-the-subscription","__idx":5},"children":["3. Confirm the subscription"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When you first register, AWS SNS sends a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SubscriptionConfirmation"]}," message to your URL. You ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["must"]}," confirm the subscription or you will not receive future notifications."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Type:"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscriptionConfirmation"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SubscribeURL:"]}," A one-time URL you must ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["GET"]}," (or ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["POST"]},") from your server. When SNS receives this request, the subscription is confirmed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your endpoint should:",{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Parse the incoming POST body as JSON."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type === \"SubscriptionConfirmation\""]},", call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscribeURL"]}," (e.g., with an HTTP GET)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Return 2xx to SNS."]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you do not confirm, the subscription stays pending and no data change notifications will be delivered."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"payload-structure","__idx":6},"children":["Payload Structure"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["StreamAPI webhooks use the standard ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["AWS SNS"]}," message envelope. The body of the POST is a single JSON object."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"top-level-sns-envelope","__idx":7},"children":["Top-level SNS envelope"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Type"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Notification"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscriptionConfirmation"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UnsubscribeConfirmation"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Unique ID for the message (use for idempotency)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TopicArn"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["SNS topic ARN (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["arn:aws:sns:us-east-1:…:Org_<orgId>"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Message"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type === \"Notification\""]},", a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["string"]}," containing a JSON object with the StreamAPI event."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Timestamp"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ISO 8601 timestamp when SNS sent the message."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SignatureVersion"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Signature version (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["1"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Signature"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Base64-encoded signature for verification."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SigningCertURL"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTPS URL to the AWS certificate used to sign the message."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["UnsubscribeURL"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["URL to unsubscribe (for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Notification"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageAttributes"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Optional attributes (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RoutingKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["organizationId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MessageType"]},")."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SubscriptionConfirmation"]},", the body also includes:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SubscribeURL"]}," – URL to call to confirm."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Token"]}," – Subscription token."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"inner-message-streamapi-event","__idx":8},"children":["Inner message (StreamAPI event)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Notification"]},", the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Message"]}," field is a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["stringified JSON"]}," object. Parse it to get the StreamAPI payload:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["messageType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DataChange"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["applicationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Application identifier."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["clientName"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Client name (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Developer"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["type"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Entity type (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OperatoryV1"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PatientV1"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["id"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Entity ID."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["operation"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CREATE"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UPDATE"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DELETE"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["payload"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Entity payload (varies by type)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["transactionId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Transaction ID."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["correlationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Correlation ID for tracing."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["sendTime"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Epoch milliseconds when the event was sent."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"messageattributes-streamapi","__idx":9},"children":["MessageAttributes (StreamAPI)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MessageAttributes"]}," on the SNS envelope often include:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Attribute"},"children":["Attribute"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["RoutingKey"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Format: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{organizationId}.{locationId}.{EntityType}.{OPERATION}"]}," (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5d111c20ca7b487854a79d0d.-1.OperatoryV1.UPDATE"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["organizationId"]}," / ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["OrganizationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Organization ID."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["locationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Location ID (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["-1"]}," if org-level)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["StreamAPI-Internal"]},"."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can use ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["RoutingKey"]}," or ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageAttributes"]}," to filter or route events without parsing the full ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Message"]}," body."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"example-notification-payload","__idx":10},"children":["Example notification payload"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Below is an example of a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Notification"]}," payload for a StreamAPI data change event."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"Type\": \"Notification\",\n  \"MessageId\": \"1899eea0-81b3-5112-88dd-8cae7f6399b5\",\n  \"TopicArn\": \"arn:aws:sns:us-east-1:393668382772:Org_5d111c20ca7b487854a79d0d\",\n  \"Message\": \"{\\\"messageType\\\":\\\"DataChange\\\",\\\"applicationId\\\":\\\"hAnIzDLqlKBpPz2hscufe4HpuGmmkpl4\\\",\\\"clientName\\\":\\\"Developer\\\",\\\"type\\\":\\\"OperatoryV1\\\",\\\"id\\\":\\\"64000000000005\\\",\\\"operation\\\":\\\"UPDATE\\\",\\\"payload\\\":{\\\"shortName\\\":\\\"testop14\\\",\\\"id\\\":\\\"64000000000005\\\",\\\"type\\\":\\\"OperatoryV1\\\"},\\\"transactionId\\\":\\\"116c84a5-33ba-4d4c-b46c-3ebce5d506e0\\\",\\\"correlationId\\\":\\\"116c84a5-33ba-4d4c-b46c-3ebce5d506e0\\\",\\\"sendTime\\\":1771371664352}\",\n  \"Timestamp\": \"2026-02-17T23:41:04.426Z\",\n  \"SignatureVersion\": \"1\",\n  \"Signature\": \"fANin6ysjQQknuP7tRSjvDrnWacHtcVrFleUWizAQ6lp1xMATOuVzDpnAEcTkAYdb7TqmeFUQXRP4DYOaRgQ6KMnvApgS1axLMI5nBEe/AdjFBeRR9GJwBNwX/0PcIDlySY+MMwU2eXg8V9ud+/RvkQPlECxm3CAJv+9ItdxXSd2jGqfQ+eVJyyLBhqnBXjwpyh7d5evwks3Dv9pB+4Mu717KpT9QAR/yCc3LXWUh4DgeCTlCZYS8xcgr0LgwVck2WIQnDBLeDkJgwYvNJLfh6Mwq2q+HaErDnccRygMsRky7Csfy41EypirEFZllSOIHAzK/oPSNtL/ehod3mlzDw==\",\n  \"SigningCertURL\": \"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem\",\n  \"UnsubscribeURL\": \"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:393668382772:Org_5d111c20ca7b487854a79d0d:f47e759a-a606-4302-82ba-f2b1bedd6618\",\n  \"MessageAttributes\": {\n    \"RoutingKey\": {\"Type\":\"String\",\"Value\":\"5d111c20ca7b487854a79d0d.-1.OperatoryV1.UPDATE\"},\n    \"organizationId\": {\"Type\":\"String\",\"Value\":\"5d111c20ca7b487854a79d0d\"},\n    \"locationId\": {\"Type\":\"String\",\"Value\":\"-1\"},\n    \"OrganizationId\": {\"Type\":\"String\",\"Value\":\"5d111c20ca7b487854a79d0d\"},\n    \"MessageType\": {\"Type\":\"String\",\"Value\":\"StreamAPI-Internal\"}\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Parsed ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Message"]}," (inner event):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"messageType\": \"DataChange\",\n  \"applicationId\": \"hAnIzDLqlKBpPz2hscufe4HpuGmmkpl4\",\n  \"clientName\": \"Developer\",\n  \"type\": \"OperatoryV1\",\n  \"id\": \"64000000000005\",\n  \"operation\": \"UPDATE\",\n  \"payload\": {\n    \"shortName\": \"testop14\",\n    \"id\": \"64000000000005\",\n    \"type\": \"OperatoryV1\"\n  },\n  \"transactionId\": \"116c84a5-33ba-4d4c-b46c-3ebce5d506e0\",\n  \"correlationId\": \"116c84a5-33ba-4d4c-b46c-3ebce5d506e0\",\n  \"sendTime\": 1771371664352\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verifying-message-signatures","__idx":11},"children":["Verifying message signatures"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To ensure messages are from AWS SNS and have not been altered, verify the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Signature"]}," using the certificate at ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SigningCertURL"]},"."]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Validate SigningCertURL"]}," – Allow only URLs from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://sns.<region>.amazonaws.com/"]}," (or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://sns.amazonaws.com/"]},"). Reject any other host."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Download the certificate"]}," – Fetch the PEM from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SigningCertURL"]}," over HTTPS (cache it by URL for a short TTL to avoid repeated downloads)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Build the string to sign"]}," – For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Notification"]},", AWS documents the canonical form using the fields: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Message"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MessageId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Subject"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Timestamp"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TopicArn"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type"]},", in that order, with each key and value followed by a newline. For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscriptionConfirmation"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UnsubscribeConfirmation"]},", use the field set that includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscribeURL"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UnsubscribeURL"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Token"]},". Follow ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"},"children":["AWS SNS Verify Signature"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Verify"]}," – Use the certificate’s public key and the algorithm (e.g., RSA-SHA1 for SignatureVersion 1) to verify the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Signature"]}," (Base64-decoded) against the string to sign."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Reject any message that fails verification."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"handling-duplicate-deliveries","__idx":12},"children":["Handling duplicate deliveries"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["SNS may deliver the same notification more than once. Use ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageId"]}," (and optionally ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["transactionId"]}," / ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["correlationId"]}," inside ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Message"]},") to deduplicate:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Store the last N ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageId"]}," values (or use a short-lived cache)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If you have already processed a given ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["MessageId"]},", return 2xx and skip processing again."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This keeps your handling idempotent and avoids duplicate side effects."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"summary-checklist","__idx":13},"children":["Summary checklist"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Step"},"children":["Step"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Action"},"children":["Action"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Expose an HTTPS endpoint that accepts POST and returns 2xx."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["2"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Register that URL as your StreamAPI webhook in the platform."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["3"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["On first request, if ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type === \"SubscriptionConfirmation\""]},", call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SubscribeURL"]}," to confirm."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["4"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Type === \"Notification\""]},", parse ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Message"]}," as JSON and process the StreamAPI event."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["5"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify the SNS signature using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SigningCertURL"]}," and reject invalid messages."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["6"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MessageId"]}," (and optionally routing attributes) for idempotency and filtering."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more on the StreamAPI and authentication, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/publicapi/api-consumer-guide"},"children":["API Consumer Guide"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/publicapi/api-best-practices"},"children":["API Best Practices"]},"."]}]},"headings":[{"value":"Overview","id":"overview","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Creating and Registering Your Webhook","id":"creating-and-registering-your-webhook","depth":2},{"value":"1. Expose a webhook endpoint","id":"1-expose-a-webhook-endpoint","depth":3},{"value":"2. Register the endpoint with the platform","id":"2-register-the-endpoint-with-the-platform","depth":3},{"value":"3. Confirm the subscription","id":"3-confirm-the-subscription","depth":3},{"value":"Payload Structure","id":"payload-structure","depth":2},{"value":"Top-level SNS envelope","id":"top-level-sns-envelope","depth":3},{"value":"Inner message (StreamAPI event)","id":"inner-message-streamapi-event","depth":3},{"value":"MessageAttributes (StreamAPI)","id":"messageattributes-streamapi","depth":3},{"value":"Example notification payload","id":"example-notification-payload","depth":2},{"value":"Verifying message signatures","id":"verifying-message-signatures","depth":2},{"value":"Handling duplicate deliveries","id":"handling-duplicate-deliveries","depth":2},{"value":"Summary checklist","id":"summary-checklist","depth":2}],"frontmatter":{"title":"Creating and Setting Up Webhooks for the StreamAPI","toc":true,"category":"getting-started","description":"How to create, configure, and secure webhooks for StreamAPI real-time data change notifications delivered via AWS SNS.","seo":{"title":"Overview"}},"lastModified":"2026-02-25T20:16:11.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/publicapi/streamapi-webhooks-setup","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}