Docs/Implementation guide

Implementation guide

Everything needed to get ReplyTime watching your client conversations: create a key, send two event types, and set your response targets. Most teams finish in under ten minutes.

API v1Last updated 11 September 2026
ReplyTime never connects to your inbox. You send it two webhooks — “client wrote” and “we replied” — and it does the timing. Nothing changes in how your team answers messages.

Quick start

1
Create an API key

In Settings → API Keys, name a key after wherever you will paste it ("Zapier — production"). Copy the secret immediately; it is shown only once.

2
Send an inbound event

Add one HTTP step to the automation that already fires when a client message arrives. POST to /api/v1/events/inbound — the countdown starts on arrival.

3
Send an outbound event

Do the same in the flow that runs when your team replies, POSTing to /api/v1/events/outbound. This is what stops the clock and records the response time.

4
Set your targets

Define a general default (say, 4 hours), then override it per channel type, per client, or per client-and-channel for your VIP accounts.

Authentication

Every request carries an API key as a bearer token. Keys are created in Settings → API Keys and shown exactly once — store it in your automation tool's credential vault, not in a plain step field.

Headerrequired
Authorization: Bearer rt_9fXk2Qm7Tb4wPz8LrN0eYh3Vc
Content-Type: application/json
Lost a key? Revoke it and create a new one — there is no way to re-reveal a secret. Revoking takes effect immediately for every automation using it.

Send an inbound event

POST/api/v1/events/inbound

Call this the moment a client writes to you. Unknown clients and channels are created on first sight — you never pre-register anything, and the countdown starts (or resets) on receipt.

FieldTypeReq.Description
client.external_refstringyesYour stable identifier for the client (CRM id, email, anything you already use). Reused across channels to group one account.
client.namestringyesDisplay name shown in the dashboard. Upserted on every event.
channel.typestringyesFree text — whatsapp, email, instagram_dm, slack, or anything you use.
channel.identifierstringyesThe channel-specific address: phone number, inbox, handle.
conversation.thread_idstringnoPass it if your source has a stable thread id — otherwise the most recent open conversation on this client/channel is reused.
message.occurred_atISO 8601yesWhen the message actually happened, not when the webhook fired.
message.senderstringnoSender identifier, if useful to record.
message.previewstringnoShort preview text of the message.
message.source_toolstringnoWhich automation sent this ("zapier", "make") — handy for debugging.
idempotency_keystringrecommendedThe source event's native id (Gmail message id, Slack ts). A repeat call with the same key is a safe no-op.
RequestcURL
curl -X POST https://replytime.net/api/v1/events/inbound \
  -H "Authorization: Bearer rt_9fXk…" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"external_ref": "atlas-legal", "name": "Atlas Legal"},
    "channel": {"type": "whatsapp", "identifier": "+90 532 118 44 07"},
    "message": {"occurred_at": "2026-09-11T14:02:11Z", "source_tool": "zapier"},
    "idempotency_key": "wamid.HBgL9f…"
  }'
Response201 Created
{
  "conversation_id": "2f6a1c9e-...",
  "sla_deadline_at": "2026-09-11T14:32:11Z"
}

Send an outbound event

POST/api/v1/events/outbound

Same body shape as above, sent when you reply. It closes the open conversation on that client.external_ref + channel pair and records the response time. Extra outbound events on an already-closed conversation return 202 and change nothing, so a chatty thread won't distort your numbers.

Body
{
  "client": {"external_ref": "atlas-legal", "name": "Atlas Legal"},
  "channel": {"type": "whatsapp", "identifier": "+90 532 118 44 07"},
  "message": {"occurred_at": "2026-09-11T14:19:40Z"}
}

Automation recipes

The same two calls, wired up in whatever you already run.

Zapier
1Trigger: new message in your channel app.2Action: Webhooks by Zapier → Custom Request, POST to /api/v1/events/inbound.3Build a second Zap the same way for your reply flow, posting to /api/v1/events/outbound.
Read the full Zapier guide →
Make
1Module: HTTP → Make a request, method POST.2Headers: Authorization: Bearer <key> + Content-Type: application/json.3Body type: Raw / JSON — map the bundle's fields into client / channel / message.
Read the full Make guide →
n8n
1Node: HTTP Request, POST, JSON body.2Auth: Generic Credential → Header Auth (Authorization: Bearer <key>).3Duplicate the node for the reply flow, pointing at /api/v1/events/outbound.
Read the full n8n guide →
Your own backend
1Fire the POST wherever you already log messages.2Send idempotency_key so retries stay safe.3Handle 401 and 429 with a short backoff before retrying.

How targets are chosen

When several SLA rules could apply to one conversation, the most specific one wins. Nothing else is consulted once a match is found.

Client + channelClientChannel typeGeneral default

Errors & retries

CodeMeaningWhat to do
201CreatedInbound event accepted. The response body includes conversation_id and sla_deadline_at.
200OKOutbound event accepted (clock stopped), or a duplicate idempotency_key returned the existing conversation as a safe no-op.
202Accepted, nothing to doOutbound event with no open conversation on record for this client/channel — nothing to stop a clock on.
400Bad requestA required field is missing or occurred_at is not valid ISO 8601. Do not retry unchanged.
401UnauthorizedKey missing, malformed, or revoked. Check the Authorization header in your automation.
429Rate limited120 events per minute per key. Back off and retry after a short delay.

Events are deduplicated on idempotency_key per tenant with no expiry — safe to let your automation tool retry indefinitely.