First call,
five minutes.
Generate a scoped key, list conversations, send a reply, register a webhook endpoint, verify signed deliveries, inspect retries, validate templates, and debug sends. No SDK install required.
Create an API key
Open Settings → API Keys, click New key, give it a label, pick scopes. Copy once. Keys starting with ir_live_ deliver real messages. Keys starting with ir_test_ never do.
# .env
IR_API_KEY=ir_live_3f2a...List your conversations
Every list endpoint is cursor-paginated. Pass ?cursor= for the next page. Max 100 per call.
curl https://api.instantreply.co/v1/conversations \
-H "Authorization: Bearer $IR_API_KEY"Send a reply
POST a message to a conversation. With a test key the call returns a fake 201 and never touches Meta. With a live key the platform handles delivery, retries, and platform compliance.
curl -X POST https://api.instantreply.co/v1/conversations/$ID/messages \
-H "Authorization: Bearer $IR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Thanks for reaching out." }'Trigger a WhatsApp Journey
The Journey Trigger API sends pre-approved WhatsApp template messages to any phone number. List available journeys with GET /v1/journeys to see required variables, then trigger with a single POST. Track delivery via the status_url in the response. See the Developer Hub for an interactive playground.
# List available journeys and their required variables
curl https://api.instantreply.co/v1/journeys \
-H "Authorization: Bearer $IR_API_KEY"
# Trigger a journey
curl -X POST https://api.instantreply.co/v1/trigger \
-H "Authorization: Bearer $IR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trigger_name": "drjob_shortlisted",
"phone": "+971501234567",
"metadata": { "1": "Ahmed", "2": "Senior Accountant", "3": "https://drjob.ae/app/8123" }
}'Register a webhook endpoint
There are two webhook flows. POST /v1/webhooks registers your server so InstantReply can send you events. POST /v1/webhooks/send is the endpoint your backend calls when it wants InstantReply to trigger a WhatsApp journey.
Create an API key with webhooks:write and webhooks:read. Point the webhook at a public HTTPS URL; for local testing, use a tunnel and register the tunnel URL.
curl -X POST https://api.instantreply.co/v1/webhooks \
-H "Authorization: Bearer $IR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/ir",
"events": [
"message.received",
"message.sent",
"conversation.created",
"conversation.closed",
"conversation.assigned",
"contact.updated",
"template.status_update",
"message.delivery_failed"
],
"description": "Production CRM sync"
}'The response includes a signing secret. Store secret immediately as IR_WEBHOOK_SECRET. It is returned once and is not shown again.
{
"id": "0d7b5e64-7b2d-4b72-b528-6f94c5089d91",
"url": "https://yourapp.com/webhooks/ir",
"events": ["message.received", "message.delivery_failed"],
"is_active": true,
"description": "Production CRM sync",
"secret": "whsec_9f2c..."
}Verify signed webhook deliveries
Every delivery includes X-InstantReply-Signature, X-InstantReply-Timestamp, X-InstantReply-Delivery, and X-InstantReply-Event. Verify sha256=HMAC_SHA256(secret, timestamp + "." + rawBody) before processing the payload. Return 2xx within 8 seconds; non-2xx or network failures retry at about 1 minute and 10 minutes.
import crypto from "node:crypto"
import express from "express"
const app = express()
app.post(
"/webhooks/ir",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.header("X-InstantReply-Signature") ?? ""
const timestamp = req.header("X-InstantReply-Timestamp") ?? ""
const deliveryId = req.header("X-InstantReply-Delivery")
const event = req.header("X-InstantReply-Event")
const rawBody = req.body.toString("utf8")
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.IR_WEBHOOK_SECRET!)
.update(`${timestamp}.${rawBody}`)
.digest("hex")
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
if (!valid) return res.sendStatus(401)
const payload = JSON.parse(rawBody)
// Store deliveryId before processing so retries stay idempotent.
console.log({ deliveryId, event, payload })
res.sendStatus(204)
},
)Inspect webhook deliveries
Webhooks are delivered at least once. Use X-InstantReply-Delivery as your idempotency key, then inspect the recent attempt history when your endpoint returns a non-2xx status or times out.
curl https://api.instantreply.co/v1/webhooks/$WEBHOOK_ID/deliveries \
-H "Authorization: Bearer $IR_API_KEY"
# Returns the last 50 attempts: status_code, attempt, delivered_at,
# next_retry_at, and created_at.Validate and submit a WhatsApp template
Before sending a template to Meta, validate it. The response reports policy risk and offers UTILITY coaching when the draft looks transactional. Rates and final category are decided by Meta; the utility_coaching field explains what to review before submission.
# Validate a stored template before submitting to Meta
curl -X POST https://api.instantreply.co/v1/templates/$TEMPLATE_ID/validate \
-H "Authorization: Bearer $IR_API_KEY"
# 200 - example response
# {
# "validation_status": "ok",
# "proposed_category": "UTILITY",
# "policy_risk": "low",
# "utility_coaching": {
# "flagged_phrases": [],
# "qualifies_as_utility": true,
# "estimated_saving_pct": 66
# }
# }
# Submit once it's clean
curl -X POST https://api.instantreply.co/v1/templates/$TEMPLATE_ID/submit \
-H "Authorization: Bearer $IR_API_KEY"Debug a delivery failure
When a message does not arrive, use GET /v1/trigger/status/:id to get a plain-English explanation: what failed, whether it is a platform or Meta issue, and the next safe step. You can also look up any Meta error code directly.
# Explain a delivery failure for a specific trigger run
curl https://api.instantreply.co/v1/trigger/status/$AUTOMATION_ACTION_ID \
-H "Authorization: Bearer $IR_API_KEY"
# Or look up a known Meta error code
curl https://api.instantreply.co/v1/developer/troubleshooting/errors/131049 \
-H "Authorization: Bearer $IR_API_KEY"
# 200 - example response
# {
# "code": "131049",
# "what_happened": "Meta throttled the marketing template for this contact.",
# "fault": "meta",
# "next_step": "Wait 24 h or switch to a UTILITY template.",
# "retryable": false
# }Next