Quickstart
In about 15 minutes you will have the chat on your site, make your first API call, and receive your first webhook.
1. Put the widget on your site
In the console, open Sites, add your website and copy its embed code. Paste it before </body> on every page where the chat should appear:
<script src="https://cdn.sealreply.com/widget.js" data-site-key="sk_…" async></script>The widget only loads on the domains you list for the site, and only after you verify the first one (a DNS TXT record or a file). See Install the widget.
2. Create an API key
Open Developers → Create API key. For this guide, tick conversations:read and messages:write. Copy the key — it starts with sr_live_ and is shown only once. Keep it on your server: anyone with the key can act for your workspace within its access.
3. Make your first call
Start a chat from your site as a visitor so there is a conversation waiting, then list waiting conversations:
curl https://api.sealreply.com/v1/conversations?status=waiting \
-H "Authorization: Bearer $SEALREPLY_API_KEY"Every response has the same shape:
{ "success": true, "data": [{ "id": "0192…", "status": "waiting", "visitor": { "display_name": "Visitor #4821" } }], "error": null, "meta": { "has_more": false, "next_cursor": null } }Now answer the oldest one as your integration. The complete program, in your language:
// Your first API calls in Node.js (18+): list waiting conversations and
// answer the oldest as your integration. Needs a key with conversations:read
// and messages:write.
const API = process.env.SEALREPLY_API_URL ?? "https://api.sealreply.com";
const KEY = process.env.SEALREPLY_API_KEY;
async function call(method, path, body) {
const res = await fetch(API + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body && JSON.stringify(body),
});
const json = await res.json();
if (!json.success) throw new Error(`${res.status} ${json.error.code}: ${json.error.message}`);
return json.data;
}
const waiting = await call("GET", "/v1/conversations?status=waiting");
console.log(`${waiting.length} waiting`);
const oldest = waiting.at(-1);
if (oldest) {
const message = await call("POST", `/v1/conversations/${oldest.id}/messages`, {
client_msg_id: crypto.randomUUID(), // makes retries safe
content_type: "text",
text: "Thanks for waiting! Someone will be with you in a minute.",
});
console.log(`replied to ${oldest.visitor.display_name}: ${message.id}`);
}"""Your first API calls in Python 3 (standard library only): list waiting
conversations and answer the oldest as your integration. Needs a key with
conversations:read and messages:write."""
import json
import os
import urllib.error
import urllib.request
import uuid
API = os.environ.get("SEALREPLY_API_URL", "https://api.sealreply.com")
KEY = os.environ["SEALREPLY_API_KEY"]
def call(method, path, body=None):
req = urllib.request.Request(
API + path,
method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req) as res:
return json.load(res)["data"]
except urllib.error.HTTPError as e:
err = json.load(e)["error"]
raise SystemExit(f"{e.code} {err['code']}: {err['message']}")
waiting = call("GET", "/v1/conversations?status=waiting")
print(f"{len(waiting)} waiting")
if waiting:
oldest = waiting[-1]
message = call("POST", f"/v1/conversations/{oldest['id']}/messages", {
"client_msg_id": str(uuid.uuid4()), # makes retries safe
"content_type": "text",
"text": "Thanks for waiting! Someone will be with you in a minute.",
})
print(f"replied to {oldest['visitor']['display_name']}: {message['id']}")<?php
// Your first API calls in PHP 7.4+ (curl extension): list waiting
// conversations and answer the oldest as your integration. Needs a key with
// conversations:read and messages:write.
$api = getenv('SEALREPLY_API_URL') ?: 'https://api.sealreply.com';
$key = getenv('SEALREPLY_API_KEY');
function sealreply_call(string $method, string $path, ?array $body = null)
{
global $api, $key;
$ch = curl_init($api . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
]);
$json = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (!$json['success']) {
fwrite(STDERR, "$status {$json['error']['code']}: {$json['error']['message']}\n");
exit(1);
}
return $json['data'];
}
$waiting = sealreply_call('GET', '/v1/conversations?status=waiting');
echo count($waiting) . " waiting\n";
if ($waiting) {
$oldest = end($waiting);
$message = sealreply_call('POST', "/v1/conversations/{$oldest['id']}/messages", [
'client_msg_id' => bin2hex(random_bytes(16)), // makes retries safe
'content_type' => 'text',
'text' => 'Thanks for waiting! Someone will be with you in a minute.',
]);
echo "replied to {$oldest['visitor']['display_name']}: {$message['id']}\n";
}//go:build ignore
// Your first API calls in Go (standard library only): list waiting
// conversations and answer the oldest as your integration. Needs a key with
// conversations:read and messages:write.
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
var (
api = envOr("SEALREPLY_API_URL", "https://api.sealreply.com")
key = os.Getenv("SEALREPLY_API_KEY")
)
func envOr(name, fallback string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback
}
// call sends a request and decodes the envelope's data into out.
func call(method, path string, body, out any) error {
var payload bytes.Buffer
if body != nil {
if err := json.NewEncoder(&payload).Encode(body); err != nil {
return err
}
}
req, err := http.NewRequest(method, api+path, &payload)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env struct {
Success bool `json:"success"`
Data json.RawMessage `json:"data"`
Error *struct{ Code, Message string }
}
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return err
}
if !env.Success {
return fmt.Errorf("%d %s: %s", res.StatusCode, env.Error.Code, env.Error.Message)
}
return json.Unmarshal(env.Data, out)
}
type conversation struct {
ID string `json:"id"`
Visitor struct {
DisplayName string `json:"display_name"`
} `json:"visitor"`
}
func main() {
var waiting []conversation
if err := call("GET", "/v1/conversations?status=waiting", nil, &waiting); err != nil {
log.Fatal(err)
}
fmt.Printf("%d waiting\n", len(waiting))
if len(waiting) == 0 {
return
}
oldest := waiting[len(waiting)-1]
id := make([]byte, 16)
rand.Read(id)
var message struct{ ID string }
err := call("POST", "/v1/conversations/"+oldest.ID+"/messages", map[string]string{
"client_msg_id": hex.EncodeToString(id), // makes retries safe
"content_type": "text",
"text": "Thanks for waiting! Someone will be with you in a minute.",
}, &message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("replied to %s: %s\n", oldest.Visitor.DisplayName, message.ID)
}Run it with SEALREPLY_API_KEY=sr_live_… node first-call.mjs (or the equivalent). The reply appears in the visitor's widget and, marked Integration, in the console. These programs are run against the API in our test suite, so they work as printed.
4. Receive a webhook
Open Developers → Webhooks → Add endpoint, enter an HTTPS URL on your server, and pick message.created. Copy the signing secret (whsec_…). Send a test event from the console, then verify signatures as shown in Receiving webhooks.
Next steps
- Authentication and limits: scopes, rate limits, monthly calls.
- Requests and errors: pagination, idempotency, error codes.
- API reference: every endpoint and field.