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.
Integration
used to send live conversations from your support platformManagement
used to configure your AI agent (e.g. in an Infrastructure-as-Code pipeline)
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:
typing
when a participant is typingdelivered
when a message has been delivered to a participantread
when a participant has read a messagejoin
andleave
for participants joining and leaving the chat
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
- Secrets are exposed as
${secrets.your_secret_name}
${conversation.id}
${conversation.customer_id}
- Simple top-level fields (e.g. strings, numbers, booleans) in the conversation
metadata are exposed as
${conversation.metadata.your_field}
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:
35.234.150.140/32
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:
- Split on the
,
character - Split each item by the
=
character to get a list of key/value pairs - Prevent replay attacks:
- Take the item with the key
t
and parse its value as a UNIX timestamp (the number of seconds since January 1st 1970) - Check the timestamp is less than 5 minutes old
- Take the item with the key
- Concatenate the UNIX timestamp, the
.
character, and the raw request body - Create an HMAC-SHA256 hash of that string, using your webhook signing key
- 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. |