NAV
shell python go

Introduction 👋

Our AI agent integrates with many popular customer support platforms out of the box, but if you have an in-house support tool or need more control, you can build your own integration using our RESTful HTTP API.

Our API is accessible at https://api.gradient-labs.ai. There are client libraries available for Python and Go.

Request and response bodies are encoded as JSON, with timestamps encoded as RFC 3339 strings.

Authentication 🔒

Requests must be authenticated by providing your API Key in the Authorization header, in the form: Bearer <your api key>.

curl https://api.gradient-labs.ai \
  --header "Authorization: Bearer $api_key"
import os
from gradient_labs import Client

client = Client(os.environ["api_key"])
package main

import (
    "log"
    "os"

    glabs "github.com/gradientlabs-ai/go-client"
)

func main() {
    client, err := glabs.NewClient(
        glabs.WithAPIKey(os.Getenv("api_key")),
    )
    if err != nil {
        log.Fatal(err)
    }
}

Roles

API Keys have one of the following roles, which limits their permissions according to their intended use.

This separation of permissions is intended to stop an API Key leaked from a CI/CD environment being used to disrupt live conversations, or exfiltrate customer data.

The role each endpoint requires is documented inline below.

Idempotence 🔁

Unless stated otherwise, all endpoints are idempotent and requests can be safely retried.

Knowledge 📖

The AI agent can make use of your help content to answer general customer questions.

Add or update a topic

Topics enable you to categorise your help articles into groups.

curl https://api.gradient-labs.ai/topics \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "topic-a1",
    "parent_id": "topic-a",
    "name": "Account Management",
    "description": "How to manage your account in our amazing app",
    "visibility": "public",
    "status": "published",
    "created": "2023-10-23T17:24:50.804952Z",
    "updated": "2024-08-20T09:27:32.682495Z"
  }
BODY
# Pending implementation.
conv, err := client.UpsertArticleTopic(ctx, glabs.UpsertArticleTopicParams{
    ID:                "topic-a-1",
    ParentID:          "topic-a",
    Name:              "Account management",
    Description:       "How to manage your account in our amazing app",
    Visibility:        glabs.VisibilityPublic,
    PublicationStatus: glabs.PublicationStatusPublished,
    Created:           time.Now(),
    LastEdited:        time.Now(),
})

Body Parameters

Parameter Type Required Description
id String Unique identifier for this topic. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =.
parent_id String Unique identifier for this topic's parent topic. Top-level topics will have no parent, but sub-topics should point back up to their parent topic.
title String The title or heading for this topic (e.g. "Account Management").
description String A description for this topic.
visibility String Identifies who can see this topic (must be one of public, users, or internal).
status String The publication status of this topic (must be one of draft or published).
data Any Arbitrary metadata to attach to this topic.
created Timestamp When this topic was first created.
last_edited Timestamp When this topic was last edited.

Required Role

Integration

Add or update an article

Articles are one kind of document that the AI agent can work with.

curl https://api.gradient-labs.ai/articles \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "article-123",
    "parent_id": "topic-a-1",
    "name": "How to change your address",
    "body": "...",
    "visibility": "public",
    "status": "published",
    "created": "2023-10-23T17:24:50.804952Z",
    "updated": "2024-08-20T09:27:32.682495Z"
  }
BODY
# Pending implementation.
conv, err := client.ArticleUpsert(ctx, glabs.UpsertArticleTopicParams{
    ID:                "article-123",
    TopicID:           "topic-a-1",
    Name:              "How to change your address",
    Description:       "How to manage your account in our amazing app",
    Body:              "...",
    Visibility:        glabs.VisibilityPublic,
    PublicationStatus: glabs.PublicationStatusPublished,
    Created:           time.Now()
    LastEdited:        time.Now()
})

Body Parameters

Parameter Type Required Description
id String Unique identifier for this article. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =.
title String The title or heading for this article (e.g. "How to change your address").
description String A description for this article.
body String The main contents of this article.
topic_id String The topic that this article sits under.
author_id String Optionally identifies the article's author.
visibility String Identifies who can see this article (must be one of public, users, or internal).
status String The publication status of this article (must be one of draft or published). The AI agent will not use draft articles.
data Any Arbitrary metadata to attach to this article.
created Timestamp When this article was first created.
last_edited Timestamp When this article was last edited.

Required Role

Integration

Conversations 💬

Conversations are the core way that your customers can interact with our AI agent.

We also send webhooks to let you know when our agent wants to reply to a customer, hand-off to a human agent, or bring the conversation to a close.

Start a conversation

To begin a conversation, make a POST request to the /conversations endpoint.

curl https://api.gradient-labs.ai/conversations \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "conversation-1234",
    "customer_id": "user-1234",
    "channel": "web",
    "created": "2023-10-23T17:24:50.804952Z"
    "metadata": {
      "chat_entrypoint": "home-page"
    }
  }
BODY
conv = client.start_conversation(
    conversation_id="conversation-1234",
    customer_id="user-1234",
    channel="web",
    metadata={
        "chat_entrypoint": "home-page",
    },
)
conv, err := client.StartConversation(ctx, glabs.StartConversationParams{
    ID:         "conversation-1234",
    CustomerID: "user-1234",
    Channel:    glabs.ChannelWeb,
    Metadata:   map[string]string{"chat_entrypoint": "home-page"},
})
{
  "id": "conversation-1234",
  "customer_id": "user-1234",
  "channel": "web",
  "metadata": {
    "chat_entrypoint": "home-page"
  },
  "created": "2023-10-23T17:24:50.804952Z",
  "updated": "2023-10-23T17:24:50.804952Z",
  "status": "active"
}

Body Parameters

Parameter Type Required Description
id String Unique identifier for this conversation. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =. Tip: use something meaningful to your business (e.g. a ticket number).
customer_id String Unique identifier for the customer. Used to build historical context of conversations the agent has had with this customer.
channel String Identifier for the way the customer is getting in touch: one of web or email.
created Timestamp The time at which the conversation started (defaults to the current time).
metadata Any Arbitrary metadata to attach to this conversation. Will be passed along with webhooks so can be used as action parameters.
assignee_id String Unique identifier for the human agent or bot that is assigned to the conversation. Used to render assignment changes in the web app.
assignee_type String Identifies the type of assignee (must be one of Agent, Bot, or AI Agent). If the conversation is started with an assignee type of AI Agent, then our AI Agent will wait a configurable amount of time for the customer to start sending messages, and close out (finish) the chat if nothing happens. If the type is any of the other options, or omitted, then our AI Agent will remain in an 'observing' state until the conversation is assigned to it.

Required Role

Integration

Add a message

To record a message sent by the customer or a human agent, make a POST request to the /conversations/:conversation_id/messages endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/messages" \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "message-1234",
    "body": "Hello! I need some help setting up my toaster oven, please.",
    "participant_id": "user-1234",
    "participant_type": "Customer",
    "created": "2023-10-23T17:24:50.804952Z",
    "metadata": {
      "device_os": "iOS 17"
    }
  }
BODY
from datetime import datetime
from gradient_labs import ParticipantType

msg = client.add_message(
    conversation_id="conversation-1234",
    message_id="message-1234",
    body="Hello! I need some help setting up my toaster oven, please.",
    participant_id="user-1234",
    participant_type=ParticipantType.CUSTOMER,
    metadata={
        "device_os": "iOS 17",
    },
    attachments=[
        Attachment(
            type=AttachmentType.IMAGE,
            file_name="toaster.jpg",
        )
    ],
)
msg, err := client.AddMessage(ctx, "conversation-1234", glabs.AddMessageParams{
    ID:              "message-1234",
    Body:            "Hello! I need some help setting up my toaster oven, please.",
    ParticipantID:   "user-1234",
    ParticipantType: glabs.ParticipantTypeCustomer,
    Metadata:        map[string]string{"device_os": "iOS 17"},
    Attachments: []*glabs.Attachment{
        {
            Type:     glabs.AttachmentTypeImage,
            FileName: "toaster.jpg",
        },
    },
})
{
  "id": "message-1234",
  "body": "Hello! I need some help setting up my toaster oven, please.",
  "participant_id": "user-1234",
  "participant_type": "Customer",
  "created": "2023-10-23T17:24:50.804952Z",
  "metadata": {
    "device_os": "iOS 17"
  }
}

Body Parameters

Parameter Type Required Description
id String Unique identifier for this message. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =. Tip: use something meaningful to your business.
body String The message text. Required if there are no attachments.
attachments List An optional list of attachments that were sent with the message. Currently, our AI agent does not read attachments and will therefore hand-off any conversation where an attachment is uploaded.
participant_id String Identifies the message sender.
participant_type String Identifies the type of sender (must be one of Customer, Agent, or Bot).
created Timestamp The time at which the message was sent (defaults to the current time).
metadata Any Arbitrary metadata to attach to this message.

Required Role

Integration

Add an event

We support logging the following conversation events:

To record an event, make a POST request to the /conversations/:conversation_id/events endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/events" \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "type": "typing",
    "participant_id": "user-1234",
    "participant_type": "Customer",
    "timestamp": "2023-10-23T17:24:50.804952Z",
  }
BODY
msg, err := client.AddConversationEvent(ctx, "conversation-1234", glabs.EventParams{
    Type:            glabs.ConversationEventTypeTyping,
    ParticipantID:   "user-1234",
    ParticipantType: glabs.ParticipantTypeCustomer,
})

Body Parameters

Parameter Type Required Description
type String Type of event. One of typing, delivered, read, join, or leave.
participant_id String Identifies the participant.
participant_type String Identifies the type of participant (must be one of Customer, Agent, or Bot).
message_id String For events that are message-specific (e.g. "read") optionally identifies the message.
timestamp Timestamp The time at which the event was created (defaults to the current time).
idempotency_key String Key to make the request idempotent.

Required Role

Integration

Assign the conversation

Assignment controls who is currently responsible for responding to the customer, it's how you activate and deactivate the AI agent.

To assign the conversation, make a PUT request to the /conversations/:conversation_id/assignee endpoint.

To assign the conversation to the AI agent, use the assignee type "AI Agent". Note: This will fail if you have not configured a webhook endpoint that we can use to deliver replies to the customer.

If you need to stop the AI agent from responding in a conversation (e.g. because a human agent has taken over), assign the conversation away from the AI agent by using one of the other available assignee types.

By default, conversations will start off without an assignment, unless you pass the assignee_id and assignee_type parameters to the start endpoint.

A conversation cannot be assigned to a customer.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/assignee" \
  --request PUT \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "assignee_id": "agent-1234",
    "assignee_type": "AI Agent",
    "timestamp": "2023-10-23T17:24:50.804952Z",
  }
BODY
from datetime import datetime
from gradient_labs import ParticipantType

client.assign_conversation(
    conversation_id=conv.id,
    participant_type=ParticipantType.AI_AGENT,
)
err = client.AssignConversation(ctx, conv.ID, &glabs.AssignmentParams{
    AssigneeType: glabs.ParticipantTypeAIAgent,
})
if err != nil {
    return err
}
{
  "assignee_id": "agent-1234",
  "assignee_type": "AI Agent",
}

Body Parameters

Parameter Type Required Description
assignee_id String Unique identifier for the human agent or bot that is assigned to the conversation. Used to render assignment changes in the web app.
assignee_type String Identifies the type of assignee (must be one of Agent, Bot, or AI Agent).
timestamp Timestamp The time at which the conversation was assigned (defaults to the current time).

Required Role

Integration

Read a conversation

To retrieve the latest status of a conversation, make a GET request to the /conversations/:conversation_id endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id" \
  --request GET \
  --header "Authorization: Bearer $api_key"
client.read_conversation(conversation_id="conversation-1234")
err := client.ReadConversation(ctx, "conversation-1234")

Required Role

Integration

End a conversation

When the conversation is finished (e.g., because the customer issue has been resolved, or the chat is being closed after the customer has abandoned it), make a PUT request to the /conversations/:conversation_id/end endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/end" \
  --request PUT \
  --header "Authorization: Bearer $api_key"
  --data @- \
<<BODY
  {
    "timestamp": "2023-10-23T17:24:50.804952Z",
  }
BODY
client.end_conversation(conversation_id="conversation-1234")
err := client.EndConversation(ctx, "conversation-1234")

Body Parameters

Parameter Type Required Description
timestamp Timestamp The time at which the conversation was ended (defaults to the current time).

Required Role

Integration

Resources 📦

Attaching resources to a conversation gives our AI agent the context it needs to help customers with their specific, individual circumstances. Examples include: order details, recent transactions, or the customer's user profile.

A resource can be any JSON document, as long it is smaller than 1MB. There are no strict requirements on the format/structure of the document, but we recommend making attribute names as descriptive as possible.

Over time, the AI agent will learn the structure of your resources - so while it's fine to add new attributes, you may want to consider using new resource names when removing attributes or changing the structure of your resources significantly.

Resource names are case-insensitive and can be anything consisting of letters, numbers, or any of the following characters: _ - + =. Names should be descriptive handles that are the same for all conversations (e.g. "order-details" and "user-profile") rather than unique identifiers.

Resources can be "pushed" directly to our API or "pulled" from an HTTP endpoint you provide. This configuration, together with our inferred structure, is called the "resource type".

Push a resource

To push a resource to a conversation, make a PUT request to the /conversations/:conversation_id/resources/:name endpoint. We'll automatically create a resource type with the given name if it doesn't already exist.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/resources/$name" \
  --request PUT \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "1234",
    "status": "shipped"
  }
BODY
err = client.AddResource(ctx, conv.ID, "order-details", struct {
    ID     string `json:"id"`
    Status string `json:"status"`
}{
    ID:     "1234",
    Status: "shipped",
})
if err != nil {
    return err
}
client.add_resource(
    conversation_id=conv.id,
    name="order-details",
    data={
        "id": "1234",
        "status": "shipped",
    },
)

Required Role

Integration

Create or update a resource type

To manually create or update a resource type, make a PUT request to the /resource-types/:name endpoint.

curl "https://api.gradient-labs.ai/resource-types/$name" \
  --request PUT \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "display_name": "User Profile",
    "description": "Includes the user's name, preferences, etc.",
    "mode": "pull",
    "pull_config": {
      "http": {
        "method": "GET",
        "url_template": "https://my-api.com/users/${conversation.customer_id}",
        "header_templates": {
          "Authorization": "Bearer ${secrets.api_key}"
        }
      },
      "cache": "10m"
    }
  }
BODY

Body Parameters

Parameter Type Required Description
display_name String Human-friendly name for the resource type that will be displayed in the web app.
description String Contextual information about the data this resource type contains.
mode String How this resource gets added to conversations (must be one of: push or pull).
pull_config Object if mode is pull Controls how this resource will be pulled (see below).

Pull Configuration

Parameter Type Required Description
http.method String The HTTP method that will be used (e.g. GET or POST).
http.url_template String The template that will be evaluated to get the pull URL.
http.header_templates Map<String, String> Map of templates that will be evaluated to get the request headers.
http.body Object unless http.method is GET The request body configuration.
http.body.encoding String The body Content-Type. Must be application/json or application/x-www-form-urlencoded
http.body.json_template String if http.body.encoding is application/json The template that will be evaluated to get the request body.
http.body.form_field_templates Map<String, String> if http.body.encoding is application/x-www-form-urlencoded Map of templates that will be evaluated to get the form fields for the request body.
cache String How long the pulled data will be used before being refreshed. Must be a Go duration string, or never if the data is valid indefinitely. Default is 10m.

Templates

Templates allow you to include dynamic data and secrets in your URLs, headers, and request bodies. To interpolate to a variable, wrap it in ${} delimiters (e.g. Hey there, ${conversation.metadata.first_name}).

Available variables

Required Role

Management

List resource types

To list your configured resource types (and the ones we've created automatically), make a GET request to the /resource-types endpoint.

curl "https://api.gradient-labs.ai/resource-types" \
  --request GET \
  --header "Authorization: Bearer $api_key" \
{
  "resource_types": [
    {
      "name": "user-profile",
      "display_name": "User Profile",
      "description": "Includes the user's name, preferences, etc.",
      "mode": "pull"
    }
  ]
}

Required Role

Management

Read a resource type

To read a resource type, including its "pull configuration" make a GET request to the /resource-types/:name endpoint.

curl "https://api.gradient-labs.ai/resource-types/$name" \
  --request GET \
  --header "Authorization: Bearer $api_key" \
{
  "name": "user-profile",
  "display_name": "User Profile",
  "description": "Includes the user's name, preferences, etc.",
  "mode": "pull",
  "pull_config": {
    "http": {
      "method": "GET",
      "url_template": "https://my-api.com/users/${conversation.customer_id}",
      "header_templates": {
        "Authorization": "Bearer ${secrets.api_key}"
      }
    },
    "cache": "10m"
  }
}

Required Role

Management

Delete a resource type

To delete a resource type, make a DELETE request to the /resource-types/:name endpoint.

curl "https://api.gradient-labs.ai/resource-types/$name" \
  --request DELETE \
  --header "Authorization: Bearer $api_key" \

Required Role

Management

Secrets 🤫

Store sensitive data such as API credentials as secrets, and the agent can use them in tools and to pull resources. Secrets will be encrypted at rest using AES-256 and access to them is strictly audited.

Create or update a secret

To store a secret, make a PUT request to the /secrets/:name endpoint.

curl "https://api.gradient-labs.ai/secrets/$name" \
  --request PUT \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "value": "s3cret s4uce"
  }
BODY

Required Role

Management

List secrets

To list your configured secrets, make a GET request to the /secrets endpoint.

curl "https://api.gradient-labs.ai/secrets" \
  --request GET \
  --header "Authorization: Bearer $api_key" \
{
  "secrets": [
    {
      "name": "api_key",
      "created": "2024-11-08T15:11:26.258766Z",
      "updated": "2024-11-08T15:11:26.258766Z"
    }
  ]
}

Required Role

Management

Revoke a secret

To permanently revoke a secret, make a DELETE request to the /secrets/:name endpoint.

curl "https://api.gradient-labs.ai/secrets/$name" \
  --request DELETE \
  --header "Authorization: Bearer $api_key" \

Required Role

Management

Hand-off Targets 🎯

Conversations can be escalated to human agents or other systems for further assistance.

Add or update a hand-off target

You can add or update hand-off targets. You can then reference them in your intents and procedures through the web app.

curl https://api.gradient-labs.ai/hand-off-targets \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "accounts",
    "name": "Accounts",
  }
BODY
client.upsert_hand_off_target(hand_off_target_id="accounts", name="Accounts")
conv, err := client.UpsertHandOffTarget(ctx, glabs.UpsertHandOffTargetParams{
    ID:   "accounts",
    Name: "Account",
})

Body Parameters

Parameter Type Required Description
id String Unique identifier for this hand-off target, it will be sent back to you in the conversation.hand_off webhook event. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =.
name String The human-friendly name of this hand-off target.

Required Role

Management

Webhooks 🪝

We deliver webhooks by making an HTTP POST request to your configured endpoint.

Our webhooks will be delivered from IP addresses in the following ranges:

You can get also get this list programmatically using the /ip-addresses endpoint to use in firewall maintenance scripts.

Request format

PUT /your-endpoint HTTP/1.1
Host: your-api.com
Content-Type: application/json
User-Agent: GradientLabs/1.0
X-GradientLabs-Signature: t=1698953919,v1=9f16975886154e02fb0bb34694baf87b83d385d7941310c55d2021e3f5cdf780

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "agent.message",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      },
      "body": "Sure, I can help you set up your toaster oven!"
    }
  }
}

The request body is a JSON object with the following structure.

Field Type Description
id String Unique identifier for this event.
type String Type of event (documented below).
sequence_number Integer Sequential index of this event within the conversation (see below).
timestamp Timestamp Time at which this event was generated.
data Object Event data.

The data object for all conversation-related webhooks contains a conversation object with the following fields.

Field Type Description
id String Your chosen unique identifier for this conversation.
customer_id String Your chosen identifier for the customer.
metadata Any Metadata you attached to the conversation.

Sequence numbers

Each of the webhooks about a particular conversation includes an incrementing number called the sequence_number, which can be used to establish a total order of events.

You can use this number to detect when events are missed or delivered in the wrong order (e.g. to prevent sending a message with sequence_number = 5 before an earlier message with sequence_number = 4).

Signature verification

import os
from flask import Flask, request
from gradient_labs import Webhook, SignatureVerificationError

app = Flask(__name__)
signing_key = os.environ["webhook_signing_key"]


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    payload = request.data.decode("utf-8")
    signature_header = request.headers.get("X-GradientLabs-Signature", None)

    try:
        event = Webhook.parse_event(
            payload=payload,
            signature_header=signature_header,
            signing_key=signing_key,
        )
    except SignatureVerificationError:
        print("Invalid signature!")
        return "Bad signature", 401

    print(
        "Received event: id={id}, type={type}".format(
            id=event.event_id, type=event.event_type
        )
    )
    return "", 200


if __name__ == "__main__":
    app.run(port=int(os.environ.get("PORT", 5000)))
package main

import (
    "errors"
    "log"
    "net/http"
    "os"

    glabs "github.com/gradientlabs-ai/go-client"
)

func main() {
    client, err := glabs.NewClient(
        glabs.WithAPIKey(os.Getenv("api_key")),
        glabs.WithWebhookSigningKey(os.Getenv("webhook_signing_key")),
    )
    if err != nil {
        log.Fatal(err)
    }

    http.ListenAndServe(":4321", webhookHandler(client))
}

func webhookHandler(client *glabs.Client) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        webhook, err := client.ParseWebhook(r)
        switch {
        case errors.Is(err, glabs.ErrInvalidWebhookSignature):
            w.WriteHeader(http.StatusUnauthorized)
            return
        case errors.Is(err, glabs.ErrUnknownWebhookType):
            log.Printf("unknown webhook type: %q", webhook.Type)
            return
        case err != nil:
            w.WriteHeader(http.StatusInternalServerError)
            log.Printf("failed to parse webhook: %v", err)
            return
        }

        // Handle the webhook...
    })
}

We sign webhooks using HMAC-SHA256, so you can tell they're really from us.

This signature is in the X-GradientLabs-Signature request header. Our client libraries can check it for you, but to do it yourself:

  1. Split on the , character
  2. Split each item by the = character to get a list of key/value pairs
  3. Prevent replay attacks:
    1. Take the item with the key t and parse its value as a UNIX timestamp (the number of seconds since January 1st 1970)
    2. Check the timestamp is less than 5 minutes old
  4. Concatenate the UNIX timestamp, the . character, and the raw request body
  5. Create an HMAC-SHA256 hash of that string, using your webhook signing key
  6. Hex-encode the hash and compare it to each item with the key v1, if one of them is a match, the request is legitimate! 🎉

Failure, timeouts, and retries

If your endpoint does not respond with a 2XX status code within 10 seconds, we'll consider webhook delivery failed, and begin retrying with exponential back-off and jitter.

Critical webhooks (agent.message, conversation.hand_off and conversation.finished) will be retried 36 times over a 24 hour period. If delivery has still not succeeded, the agent will mark the conversation as failed and stop processing it.

Action webhooks (action.execute) will be retried for up to 1 minute, and if they still have not succeeded, the conversation will be handed off to a human agent.

agent.message

We send this event when the agent wants to send a message to the customer.

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "agent.message",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    },
    "body": "Sure, I can help you set up your toaster oven!"
  }
}

Payload Fields

Field Type Description
body String Text of the message the agent wants to send to the customer.

conversation.hand_off

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "conversation.hand_off",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    }
  }
}

We send this event when the agent is escalating or handing the conversation off to a human agent.

conversation.finished

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "conversation.finished",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    }
  }
}

We send this event when the agent has concluded the conversation with the customer and you should close any corresponding ticket, etc.

action.execute

We send this event when the agent wants to execute one of your configured webhook-backed actions.

For data-retrieval actions, you should respond with a JSON object containing the data. Note: don't have to tell us the response schema up-front, the agent is able to extract data from any well-formed JSON object using AI. 🪄

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "action.execute",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    },
    "action": "random-dog-fact",
    "params": {
      "breed": "Golden Retriever"
    }
  }
}

Payload Fields

Field Type Description
action String Your given identifier for this action.
params Object Parameters the agent has generated for this action.