Card Sync Flowchart

Mirror card creates, updates, and archives from Guru's webhooks into any external tool.

When a card changes in Guru, Guru's webhooks emit an event to your endpoint. Your integration then makes the API calls needed to mirror that change in an external tool, whether that's Salesforce, Zendesk, Confluence, or any other system with a REST API. The three flows below cover the full lifecycle of a card.

The webhook payload

Every card event arrives in the same envelope; only the event type changes. This example shows a card-created event with the webhook's deliveryMode set to BATCH, the recommended mode:

{
  "items": [
    {
      "webhookId": "RVxvFQ",
      "source": "channel.message",
      "serial": "01788968371604-000@6f8GnB8uAC6d0U81532972",
      "timestamp": 1788968371604,
      "name": "channel.message",
      "data": {
        "channelId": "wh:f13d7e8a-b011-4b99-9071-bc2800238945",
        "site": "us-west-1-A",
        "messages": [
          {
            "id": "3e276ee9-857a-4ec6-993d-36c18ff32d72:0",
            "timestamp": 1788968371604,
            "data": "{\"user\":\"[email protected]\",\"properties\":{\"cardId\":\"1bd32498-9672-4663-ab80-c0931db6d976\",\"source\":\"UI\",\"collectionId\":\"796ea6f8-6132-4202-a1db-64635f1cf2e7\"},\"id\":\"dc5d2df3-eb59-4276-b991-5523ea8cb20a\",\"eventType\":\"card-created\",\"eventDate\":\"2026-09-09T15:39:29.891+0000\"}",
            "action": 0,
            "serial": "01788968371604-000@6f8GnB8uAC6d0U81532972:000",
            "name": "card-created"
          }
        ]
      }
    }
  ]
}
❗️

Parse the data field twice

Each message's data field is a JSON-encoded string, not a nested object. Parse the request body first, then parse each message's data to get the event. A single envelope can also carry multiple messages, so iterate over messages rather than assuming one event per delivery.

import json
 
envelope = json.loads(request_body)
for item in envelope["items"]:
    for message in item["data"]["messages"]:
        event = json.loads(message["data"])
        print(event["eventType"], event["properties"]["cardId"])

The parsed event looks like this (this is also exactly what a SINGLE delivery mode request body looks like):

{
  "user": "[email protected]",
  "properties": {
    "cardId": "1bd32498-9672-4663-ab80-c0931db6d976",
    "source": "UI",
    "collectionId": "796ea6f8-6132-4202-a1db-64635f1cf2e7"
  },
  "id": "dc5d2df3-eb59-4276-b991-5523ea8cb20a",
  "eventType": "card-created",
  "eventDate": "2026-09-09T15:39:29.891+0000"
}
FieldDescription
userEmail address of the user who performed the action.
properties.cardIdID of the card the event refers to. Use it to fetch the card or look up the external record.
properties.sourceWhere the action originated, for example UI.
properties.collectionIdID of the collection the card belongs to.
idUnique ID of the event.
eventTypeThe event name: card-created, card-updated, or card-deleted.
eventDateISO 8601 timestamp of when the event occurred.

If your webhook's deliveryMode is SINGLE instead, there is no envelope: the request body is exactly the event object shown above, one request per event, with no second parse needed. BATCH is recommended because it delivers events more efficiently at higher frequencies, while SINGLE can cause delivery backups when many card events fire in a short window. See Creating a Webhook for details.

Sync only one collection

Webhooks fire for card events across your whole team; there is no way to scope a webhook to a single collection in Guru. If you only want to sync cards from a particular collection, filter on your side using the properties.collectionId field in each event before making any API calls:

TARGET_COLLECTION = "796ea6f8-6132-4202-a1db-64635f1cf2e7"
 
event = json.loads(message["data"])
if event["properties"].get("collectionId") != TARGET_COLLECTION:
    continue  # card belongs to a different collection, skip it

For archive events, the lookup step in the flow below doubles as a filter: if no external record has the incoming card ID stored on it, the card was never synced, so there is nothing to remove.

Filtering by collection also means deciding what happens when a card moves out of the synced collection. Once it moves, your filter skips its events, so the record already created in the external tool is not cleaned up automatically. If cards move between collections on your team, plan for that case, for example by periodically reconciling the external records against the collection's current cards.

When a card is created

%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "lineColor": "#9C9C9C"}, "flowchart": {"nodeSpacing": 40, "rankSpacing": 45, "padding": 12}}}%%
flowchart TB
    W1(["Guru emits<br/>card-created webhook"])
    A1["Your endpoint receives<br/>the event payload"]
    B1["Get full card details<br/>from the Guru API"]
    C1["POST a new record<br/>to the external tool"]
    Z1["Find the external record by its stored Guru card ID"]
    W1 --> A1 --> B1 --> C1
 
    click B1 "https://developer.getguru.com/reference/getv1cardsgetextendedfact" "Get card endpoint reference"
 
    classDef webhook fill:#56D886,stroke:#56D886,color:#080B0E
    classDef guru fill:#F0F0F0,stroke:#9C9C9C,color:#080B0E,rx:8,ry:8
    classDef external fill:#080B0E,stroke:#080B0E,color:#FFFFFF,rx:8,ry:8
    classDef spacer fill:none,stroke:none,color:transparent
    class W1 webhook
    class A1,B1 guru
    class C1 external
    class Z1 spacer

Use Get card to fetch the full card content, then store the Guru card ID on the external record when you create it. The update and archive flows depend on this reference to find the right record later. In Salesforce this might be a custom field like guru_card_id__c; in other tools, use whatever custom field or metadata mechanism is available.

When a card is updated

%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "lineColor": "#9C9C9C"}, "flowchart": {"nodeSpacing": 40, "rankSpacing": 45, "padding": 12}}}%%
flowchart TB
    W2(["Guru emits<br/>card-updated webhook"])
    A2["Your endpoint receives<br/>the event payload"]
    B2["Get full card details<br/>from the Guru API"]
    C2["Find the external record<br/>by its stored Guru card ID"]
    D2["PATCH the record<br/>in the external tool"]
    Z2["Find the external record by its stored Guru card ID"]
    W2 --> A2 --> B2 --> C2 --> D2
 
    click B2 "https://developer.getguru.com/reference/getv1cardsgetextendedfact" "Get card endpoint reference"
 
    classDef webhook fill:#56D886,stroke:#56D886,color:#080B0E
    classDef guru fill:#F0F0F0,stroke:#9C9C9C,color:#080B0E,rx:8,ry:8
    classDef external fill:#080B0E,stroke:#080B0E,color:#FFFFFF,rx:8,ry:8
    classDef spacer fill:none,stroke:none,color:transparent
    class W2 webhook
    class A2,B2 guru
    class C2,D2 external
    class Z2 spacer

Fetch the updated content with Get card, then search the external tool for the record whose stored Guru card ID matches the properties.cardId in the event and apply the changes.

When a card is archived

📘

Archiving is Guru's removal action

Guru emits the card-deleted event when a card is archived in Guru. Treat this event as the signal to remove the corresponding record from the external tool.

%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "lineColor": "#9C9C9C"}, "flowchart": {"nodeSpacing": 40, "rankSpacing": 45, "padding": 12}}}%%
flowchart TB
    W3(["Guru emits<br/>card-deleted webhook"])
    A3["Your endpoint receives<br/>the event payload"]
    B3["Find the external record<br/>by its stored Guru card ID"]
    C3["DELETE the record<br/>in the external tool"]
    Z3["Find the external record by its stored Guru card ID"]
    W3 --> A3 --> B3 --> C3
 
    classDef webhook fill:#56D886,stroke:#56D886,color:#080B0E
    classDef guru fill:#F0F0F0,stroke:#9C9C9C,color:#080B0E,rx:8,ry:8
    classDef external fill:#080B0E,stroke:#080B0E,color:#FFFFFF,rx:8,ry:8
    classDef spacer fill:none,stroke:none,color:transparent
    class W3 webhook
    class A3,B3 guru
    class C3 external
    class Z3 spacer

No call to the Guru API is needed here, since the card is no longer available. The event's properties.cardId is enough to find and remove the external record. If your destination tool supports archiving instead of hard deletion, you may prefer that to preserve history and mirror Guru's own behavior.

Implementation notes

  • The webhook event contains the card ID (properties.cardId) but not the full card content, which is why the create and update flows call Get card before writing to the external tool.
  • Configure which events your webhook receives with the filter field when creating the webhook, for example card-created,card-updated,card-deleted.
  • The filter field on a webhook scopes which event types are delivered, not which collections. To sync a single collection, filter on properties.collectionId in your receiver as described above.
  • In the diagrams above, green is the webhook Guru emits, grey steps interact with Guru, and black steps call the external tool's API.