> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://apidocs.polytomic.com/2023-04-25/events/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://apidocs.polytomic.com/_mcp/server. Events are a way of being notified of important actions happening within an organization. For example, Polytomic will create a sync.completed event any time a sync finishes. These events are accessible via the events API. We also offer the ability to set up outgoing webhooks which will send realtime events as they happen. Polytomic is designed for at most once delivery, which means we may fail to send some events. Additionally, events cannot be re-sent once they have been delivered. Therefore, if your use case demands a high level of assurance that all changes to a resource are processed without any missing events, we advise building a fallback polling system that periodically fetches the desired events using the events endpoint. #### Retention \ > **Note** > > Events accessible via the events endpoint are persisted for 48 hours, after which they will not be available. ## Event Types The event types are as follows: * `sync.running` * `sync.failed` * `sync.canceled` * `sync.completed` * `sync.completed_with_errors` * `bulk_sync.running` * `bulk_sync.completed` * `bulk_sync.canceled` * `bulk_sync.failed` * `bulk_sync.completed_with_error` ## Webhooks Webooks can be set up using the webhook API endpoints. Each webhook will fire events for the organization in which it exists. Currently, only one webhook per organization is allowed. ## HMAC Validation HMAC validation can be used to ensure the events you receive are legitimate. When creating or updating a webook, there's a required `secret` field. Polytomic will hash each message and include a `Polytomic-Signature` header in each POST using that secret. You can use the `secret` field to validate that the signature matches. ## Delivery We require that the endpoint returns a `2xx` response code. If a `2xx` is not returned, the event will be retried 5 times with exponential backoff. There are no guarantees around ordering. ## Record logs Note that the `sync.complete` event payload (example illustrated at the bottom of this page) also contains logs (split into JSON files) of records that were inserted or updated by Polytomic. See keys `total_records`, `inserted_records`, and `updated_records` in the event payload. ## Event payload examples ```json { "type": "bulk_sync.running", "event": { "name": "Asana to BigQuery sync", "organization_id": "be80a27e-0e80-4dcb-bee9-1666f02eeb83", "sync_id": "dcc891b3-4a25-4b8b-bba8-48104cd66525", "execution_id": "8114c8cc-99fc-4fb4-ab72-28308762fa63", "source_connection_id": "ad56197c-1bca-4256-a410-fb1ffde295c0", "destination_connection_id": "318dba62-d875-11ed-b59b-ea7534cffcab" } } ``` \ ```json { "type": "bulk_sync.completed", "event": { "name": "Asana to BigQuery sync", "organization_id": "be80a27e-0e80-4dcb-bee9-1666f02eeb83", "sync_id": "dcc891b3-4a25-4b8b-bba8-48104cd66525", "execution_id": "8114c8cc-99fc-4fb4-ab72-28308762fa63", "source_connection_id": "ad56197c-1bca-4256-a410-fb1ffde295c0", "destination_connection_id": "318dba62-d875-11ed-b59b-ea7534cffcab" } } ``` \ ```json { "type": "sync.running", "event": { "name": "Salesforce Sync", "organization_id": "be80a27e-0e80-4dcb-bee9-1666f02eeb83", "execution_id": "2a42c650-b741-4282-a4c7-19de797aa18b", "sync_id": "d09ceb2a-1641-4dc8-bdfe-d019d8964043", "target_connection_id": "7c67e0b3-9759-44eb-b96c-2a7042b583f0" } } ``` \ ```json { "type": "sync.completed", "event": { "name": "Salesforce Sync", "organization_id": "be80a27e-0e80-4dcb-bee9-1666f02eeb83", "sync_id": "d09ceb2a-1641-4dc8-bdfe-d019d8964043", "execution_id": "2a42c650-b741-4282-a4c7-19de797aa18b", "status": "completed", "total_records": [ "https://app.polytomic.com/api/syncs/54d2d580-d910-4bc7-834b-92d57ca89762/executions/c9fd1b24-0b00-46e8-905b-839f919adffd/records/log1688189538-0d74ec71-4540-4706-867f-e6263070e058.json" ], "inserted_records": null, "updated_records": [ "https://app.polytomic.com/api/syncs/54d2d580-d910-4bc7-834b-92d57ca89762/executions/c9fd1b24-0b00-46e8-905b-839f919adffd/updates/log1688189542-05f04ffa-3c85-41eb-8333-9d951f93405b.json", "https://app.polytomic.com/api/syncs/54d2d580-d910-4bc7-834b-92d57ca89762/executions/c9fd1b24-0b00-46e8-905b-839f919adffd/updates/log1688189552-6c0bcf4a-b1ae-4cf5-8ee2-dcb6d6fbb728.json" "trigger": "manual", "target_connection_id": "7c67e0b3-9759-44eb-b96c-2a7042b583f0" } } ``` ## Consuming Events Below is a brief example (written in Go) of consuming events from a webhook: ```go package main import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "io/ioutil" "net/http" "github.com/gin-gonic/gin" ) var key = []byte("somepassword") func main() { r := gin.Default() r.POST("/webhook", func(c *gin.Context) { body, _ := ioutil.ReadAll(c.Request.Body) hash := c.Request.Header.Get("Polytomic-Signature") sig, err := hex.DecodeString(hash) if err != nil { panic(err) } mac := hmac.New(sha256.New, key) mac.Write(body) if !hmac.Equal(sig, mac.Sum(nil)) { panic("Invalid signature") } fmt.Printf("Headers: %+v, Body: %s", c.Request.Header, string(body)) c.Status(http.StatusOK) }) r.Run("0.0.0.0:8000") } ```