Events

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

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

{
	"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"
   }
}
{
	"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"
  }
}
{
	"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"
	}
}
{
	"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": [],
    "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"
		],
    "errored_records": [
      "https://app.polytomic.com/api/syncs/54d2d580-d910-4bc7-834b-92d57ca89762/executions/c9fd1b24-0b00-46e8-905b-839f919adffd/errors/log1688189542-05f04ffa-3c85-41eb-8333-9d951f93405b.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:

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")
}