ArgorantDocs

Webhooks

Get a signed call to your server when a reply comes in, someone is interested, an email bounces, a list or an export is ready. Included from the Pro plan.

Events

EventWhenWhat data holds
reply.receivedA person replied to one of your campaigns. Out-of-office and other automatic replies are left outclassification, lead_email, campaign_id, subject, snippet
reply.positiveA reply was labelled interestedThe same as above
email.bouncedA campaign email bouncedlead_email, campaign_id, subject
email.unsubscribedSomeone asked to stop, complained, or used the opt-out linkclassification, lead_email, campaign_id, subject, snippet, and source for the opt-out link
campaign.pausedSending paused: the month's campaign emails of your plan are used up, or the campaign paused itself (for example too many bounces)campaign_id, campaign_name, reason, and campaign_emails (plan) or message, metrics, url (auto-pause)
campaign.resumedThe month started again or the plan grew, so sending goes oncampaign_id, campaign_name, reason, campaign_emails
export.readyAn export finishedjob_id, status, total_rows, verified_rows, status_url, download_url
list.readyA list finished filling, also after you added more peoplelist_id, list_name, job_id, status, total_items, note, status_url
verification.completedA single email check with an API key finishedemail, status, deliverable, is_role_based, is_free, is_disposable
segment.new_matchesA saved list has new people who match its filterslist_id, list_name, previous_total, current_total, new_matches

Campaign events go to the webhooks of the workspace owner and of every active team member, so each person can point them at their own tools. Exports, lists and checks go to the webhooks of the person who started them.

Create a webhook

Shell
curl -X POST "https://app.argorant.com/api/v1/webhooks" \
  -H "Authorization: Bearer $ARGORANT_API_KEY" -H "Idempotency-Key: hook-crm-1" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/argorant","events":["reply.positive","email.bounced","export.ready"]}'

The address must be a public https:// address. The answer holds the signing secret. It is shown only once, so store it now. Each account can have up to 10 webhooks, switched off ones included.

CallWhat it does
GET /api/v1/webhooksYour webhooks and every event you can choose
PATCH /api/v1/webhooks/{id}Change the URL or the events, or switch it off with "active": false
DELETE /api/v1/webhooks/{id}Remove it
POST /api/v1/webhooks/{id}/testSend one signed test call
GET /api/v1/webhooks/{id}/deliveriesRecent calls with their result and the next try

What your server receives

HTTP
POST /hooks/argorant
Content-Type: application/json
User-Agent: Argorant-Webhooks/1.0
X-Argorant-Event: reply.positive
X-Argorant-Delivery: 8841
X-Argorant-Signature: sha256=5d41402abc4b2a76b9719d911017c592...

{"event": "reply.positive", "delivery_id": "8841", "created_at": "2026-10-07T09:14:03+00:00",
 "data": {"classification": "positive", "lead_email": "jane.doe@acme.com", "campaign_id": "7f3a2c1e-9b4d-4e8a-a1c2-5d6e7f8a9b0c", "subject": "Re: Quick question", "snippet": "Happy to talk..."}}

Answer with any 2xx status within a few seconds. Do the slow work after you answered. Redirects are not followed, so use the final address.

Check the signature

The signature is the HMAC-SHA256 of the raw body with your secret. Compare it before you trust the call. Use the body bytes exactly as they arrived, before any JSON parsing.

Python
import hmac, hashlib

def is_from_argorant(secret: str, raw_body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)
Node
const crypto = require("crypto");

function isFromArgorant(secret, rawBody, signatureHeader) {
  const expected = Buffer.from("sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex"));
  const given = Buffer.from(signatureHeader || "");
  return given.length === expected.length && crypto.timingSafeEqual(expected, given);
}

The signed body holds delivery_id and created_at, so neither can be changed without breaking the signature. Keep the delivery ids you handled and skip one you have seen before. That makes a replayed call harmless.

When your server is down

A call that fails is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, so up to 6 times. X-Argorant-Delivery stays the same for every try of one event, so use it to skip events you already handled. After 50 failed calls in a row the webhook is switched off; switch it on again with PATCH and "active": true.

On this page