Skip to main content

Webhook Setup

Webhooks send HTTP POST requests to your server when events occur in SendSeven -- a message is received, a conversation is closed, a contact is created, and more. This enables real-time integrations without polling the API.

Required Scopes​

ScopePurpose
webhooks:readList and view webhooks
webhooks:createCreate new webhook endpoints
webhooks:updateUpdate webhook configuration
webhooks:deleteDelete webhooks

How Webhooks Work​

  1. You register a webhook endpoint URL and select events to subscribe to
  2. SendSeven immediately sends a one-time verification request to your URL; your endpoint must echo the challenge back to prove ownership (see Verify your endpoint)
  3. Once verified, when a subscribed event occurs SendSeven sends an HTTP POST to your URL
  4. Your server processes the event and returns a 2xx status code to acknowledge receipt
  5. If delivery fails, SendSeven retries with exponential backoff

Endpoint Requirements​

Your webhook endpoint must:

  • Use HTTPS (HTTP endpoints are not accepted)
  • Return a 2xx status code within 10 seconds (the connection itself must open within 5 seconds)
  • Accept POST requests with a JSON body
  • Be publicly accessible from the internet. If a firewall restricts inbound traffic, allow SendSeven's outbound IP addresses
  • Answer directly, without a redirect. SendSeven does not follow redirects: a 3xx response counts as a failed delivery (also for the verification request), so register the final URL
  • Resolve only to public IP addresses. The address is checked again on every delivery, not just when you save the webhook. Private, loopback, link-local, carrier-grade NAT (100.64.0.0/10) and other non-public addresses (IPv4 and IPv6) are refused, and the delivery fails with Webhook URL is not allowed

The delivery history stores the first 1,000 characters of your endpoint's response body.

IP Allowlisting​

All outbound requests from SendSeven come from these three fixed IPv4 addresses. That covers webhook deliveries, verification requests, test events and manual retries, as well as other outbound HTTP calls SendSeven makes on your behalf (for example API Call nodes in flows):

IPv4 address
136.92.3.138
34.107.59.177
35.246.202.227
  • Allowlist all three. Any single delivery may come from any of these addresses, and consecutive deliveries to the same endpoint can come from different ones. Allowing only one or two will cause intermittent failures.
  • IPv4 only. SendSeven does not send outbound traffic over IPv6, so no IPv6 rule is needed (your endpoint's hostname still needs an IPv4 A record).
  • Changes are announced in advance. If this list ever changes, we announce it ahead of time and keep the old addresses working during a transition period, so you can update your rules without downtime.

Troubleshooting: if deliveries or the verification request fail with a connection timeout (the connection never opens within 5 seconds) even though your endpoint works when you call it yourself, a firewall, WAF, hosting provider or bot protection layer is most likely dropping SendSeven's requests. Check that all three addresses above are allowed through every layer in front of your server, then trigger verification again (see Verify your endpoint).

Create a Webhook​

POST /api/v1/webhook-endpoints

Request Body​

FieldTypeRequiredDescription
namestringYesUser-friendly name for the webhook (1-255 chars)
urlstringYesHTTPS endpoint URL
subscribed_eventsarrayYesArray of event types to subscribe to
authorization_headerstringNoAuthorization header value (e.g., Bearer token123)
retry_strategystringNoexponential (default), linear, or none
max_retriesintegerNoMax retry attempts (default: 8, max: 15)
timeout_secondsintegerNoPer-attempt timeout in seconds (default: 10, accepted range 5-60). Every attempt is capped at 10 seconds, so values above 10 have no effect.
source_filter_modestringNoall (default) receives events from every channel; selected restricts them to the channels/integrations below
filtered_channel_idsarrayNoChannel IDs to receive events from (only used when source_filter_mode is selected)
filtered_email_integration_idsarrayNoEmail integration IDs to receive events from (only used when source_filter_mode is selected)

curl​

curl -X POST "https://api.sendseven.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM Integration",
"url": "https://yourapp.com/webhooks/sendseven",
"subscribed_events": [
"message.received",
"message.sent",
"conversation.created",
"conversation.closed",
"contact.created"
]
}'

Response (201 Created)​

Creation returns the signing secret, not the full endpoint object. Note the field name: it is webhook_id, not id.

{
"webhook_id": "wh_abc123",
"secret_key": "whsec_9f2c4b8e1d6a7053ef4c1b9a8d2e6f30",
"message": "Store this secret key securely. It will not be shown again."
}
Save secret_key now

This is the only time the secret is ever returned. You need it to verify webhook signatures, and it cannot be retrieved later -- only replaced via POST /api/v1/webhook-endpoints/{webhook_id}/regenerate-secret, which invalidates the old one.

Use the returned webhook_id with GET /api/v1/webhook-endpoints/{webhook_id} if you need the full endpoint object (its own id field carries the same value).

Verify Your Endpoint​

Immediately after creation (and again whenever you change the endpoint's url, or call the verify endpoint below), SendSeven sends a one-time verification request to your URL. This is a challenge-response handshake that proves you control the endpoint. The webhook stays inactive and receives no events until it is verified (is_verified: false on the endpoint object).

This is the one request that is NOT signed

The verification request does not carry an X-Sendseven-Signature header. It is the only exception to signature verification — every real event delivery is signed. A handler written strictly against the Signature Verification guide will reject this request with a 401 unless you special-case it. See the note in that guide.

The verification request​

SendSeven sends:

  • Method: POST to your registered webhook URL

  • Headers:

    HeaderExampleNotes
    Content-Typeapplication/json
    X-Sendseven-EventverificationIdentifies this as the verification handshake, not a real event
    X-Sendseven-Timestamp1770000000Unix timestamp (seconds)
    AuthorizationBearer …Only if you set authorization_header when creating the webhook

    There is no X-Sendseven-Signature header on this request.

  • Body:

    {
    "type": "sendseven_verification",
    "challenge": "a1b2c3d4e5f6...",
    "webhook_id": "wh_abc123",
    "timestamp": "2026-02-10T18:00:00Z"
    }

The response SendSeven expects​

Your endpoint must reply with:

  • Status: 200 OK

  • Body: JSON echoing the challenge back unchanged:

    { "challenge": "a1b2c3d4e5f6..." }

Returning the challenge as plain text, under a different key, or a bare 200 with no body will not verify the endpoint. You must respond within the endpoint's timeout_seconds (default and maximum 10s), and the challenge itself is valid for 5 minutes from when it was issued.

If your endpoint responds correctly, it becomes is_verified: true and is_active: true and starts receiving events. If it does not, the webhook is created but stays unverified — fix your handler and re-trigger verification:

POST /api/v1/webhook-endpoints/{webhook_id}/verify
curl -X POST "https://api.sendseven.com/api/v1/webhook-endpoints/wh_abc123/verify" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

Example handler​

A minimal endpoint that handles both the unsigned verification request and signed events:

from fastapi import FastAPI, Request, Response, HTTPException

app = FastAPI()


@app.post("/webhooks/sendseven")
async def handle_webhook(request: Request):
body = await request.body() # raw bytes — capture before parsing

# The verification handshake is unsigned. Detect it and echo the challenge.
if request.headers.get("X-Sendseven-Event") == "verification":
import json
payload = json.loads(body)
return {"challenge": payload["challenge"]}

# Every real event IS signed — verify before trusting it.
# (see the Signature Verification guide for verify_signature)
signature = request.headers.get("X-Sendseven-Signature", "")
timestamp = request.headers.get("X-Sendseven-Timestamp", "")
if not verify_signature(body, signature, timestamp):
raise HTTPException(status_code=401, detail="Invalid signature")

# ... handle the event ...
return Response(status_code=200)

List Webhooks​

GET /api/v1/webhook-endpoints
curl -X GET "https://api.sendseven.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"

This endpoint returns a bare JSON array of endpoint objects -- there is no items / pagination envelope, because a tenant's webhook list is small and unpaginated:

[
{
"id": "wh_abc123",
"tenant_id": "tenant_xyz",
"name": "CRM Integration",
"url": "https://yourapp.com/webhooks/sendseven",
"has_authorization_header": false,
"subscribed_events": [
"message.received",
"message.sent",
"conversation.created",
"conversation.closed",
"contact.created"
],
"source_filter_mode": "all",
"filtered_channel_ids": null,
"filtered_email_integration_ids": null,
"is_active": true,
"is_verified": false,
"retry_strategy": "exponential",
"max_retries": 8,
"timeout_seconds": 10,
"last_success_at": "2026-02-10T18:04:11Z",
"last_failure_at": null,
"last_error": null,
"consecutive_failures": 0,
"created_at": "2026-02-10T18:00:00Z",
"updated_at": null
}
]

Update a Webhook​

Update a webhook's URL, events, or active status. Only include the fields you want to change.

PATCH /api/v1/webhook-endpoints/{webhook_id}
FieldTypeDescription
namestringUpdated name
urlstringUpdated endpoint URL
subscribed_eventsarrayUpdated event subscriptions (replaces existing list)
is_activebooleanEnable or disable the webhook
authorization_headerstringUpdated auth header (empty string to remove)
curl -X PATCH "https://api.sendseven.com/api/v1/webhook-endpoints/wh_crm_sync" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"subscribed_events": [
"contact.created",
"contact.updated",
"conversation.created",
"conversation.closed",
"message.received"
]
}'
warning

Updating subscribed_events replaces the entire event list. Include all events you want to subscribe to, not just the new ones.

Delete a Webhook​

DELETE /api/v1/webhook-endpoints/{webhook_id}
curl -X DELETE "https://api.sendseven.com/api/v1/webhook-endpoints/wh_analytics" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

View Delivery History​

Check delivery status and response times for a webhook:

GET /api/v1/webhook-endpoints/{webhook_id}/deliveries
ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger50Items per page (1--100)
statusstring--pending, success, failed, retrying, dead_letter, or queued (stored while the endpoint is suspended -- see Circuit breaker)
event_typestring--e.g. message.sent, conversation.created
curl -X GET "https://api.sendseven.com/api/v1/webhook-endpoints/wh_crm_sync/deliveries?page=1&page_size=10" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"

This endpoint's pagination fields are flat (total, page, page_size, has_more) rather than nested under a pagination object:

{
"items": [
{
"id": "del_001",
"tenant_id": "tenant_xyz",
"webhook_endpoint_id": "wh_crm_sync",
"event_type": "contact.created",
"event_id": "evt_5f1c9d",
"attempt_number": 1,
"status": "success",
"response_status_code": 200,
"response_time_ms": 145,
"error_message": null,
"next_retry_at": null,
"created_at": "2026-02-10T17:30:00Z",
"completed_at": "2026-02-10T17:30:00Z",
"can_retry": false
},
{
"id": "del_002",
"tenant_id": "tenant_xyz",
"webhook_endpoint_id": "wh_crm_sync",
"event_type": "message.received",
"event_id": "evt_7a3b2e",
"attempt_number": 3,
"status": "failed",
"response_status_code": 500,
"response_time_ms": 2300,
"error_message": "Internal Server Error",
"next_retry_at": null,
"created_at": "2026-02-10T17:35:00Z",
"completed_at": "2026-02-10T17:35:02Z",
"can_retry": true
}
],
"total": 156,
"page": 1,
"page_size": 10,
"has_more": true
}

Deliveries with can_retry: true (status failed or dead_letter) can be replayed manually -- see the retry endpoint below.

Retry Policy​

If your endpoint returns a non-2xx status code or times out, SendSeven retries. With the default exponential strategy, the delay before each retry grows and then caps at 30 minutes:

RetryDelay after the failed attempt
1st retry5 seconds
2nd retry10 seconds
3rd retry30 seconds
4th retry2 minutes
5th retry5 minutes
6th retry10 minutes
7th retry and beyond30 minutes (capped)

Retries continue until max_retries attempts have been made (default 8, configurable 0–15). With linear strategy, retries are spaced a fixed 60 seconds apart; with none, no retries are attempted. After the retry budget is exhausted, the delivery is marked as permanently failed.

Manually retry a delivery​

A delivery whose can_retry is true (status failed or dead_letter) can be replayed on demand. This creates a new delivery attempt; the original row is left in place for audit.

POST /api/v1/webhook-endpoints/{webhook_id}/deliveries/{delivery_id}/retry

Required Scope: webhooks:update

curl -X POST "https://api.sendseven.com/api/v1/webhook-endpoints/wh_crm_sync/deliveries/del_002/retry" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
{
"success": true,
"delivery_id": "del_009",
"previous_delivery_id": "del_002",
"message": "Delivery queued for retry"
}

Circuit breaker: suspension, not silent loss​

If a webhook accumulates 20 consecutive failures, SendSeven suspends it for a bounded 12-hour window rather than disabling it outright. During suspension:

  • Matching events are not dropped — they are stored as queued deliveries (up to 10,000 per endpoint) and replayed in original order once the endpoint recovers.
  • SendSeven re-verifies your endpoint automatically on an escalating schedule: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h after suspension.
  • You receive email notifications across the lifecycle (suspended, reactivated, permanently disabled).

The endpoint's state is visible on the endpoint object: suspended_at is set while suspended, next_reactivation_at holds the next automatic check, and reactivation_attempts counts checks made so far.

OutcomeWhat happens
A re-verification succeeds (automatic or triggered by you)is_active returns to true, suspended_at clears, and the queued backlog is replayed in created_at order
Verification succeeds but the first replayed event cannot open a connection (timed out, refused or reset)The endpoint is suspended again without using up a step of the schedule and is checked again later; the 12-hour window still applies
The final check at +12h still failsThe endpoint is permanently deactivated and the queued deliveries are moved to dead_letter

To recover immediately without waiting for the next scheduled check, fix your endpoint and trigger verification yourself:

curl -X POST "https://api.sendseven.com/api/v1/webhook-endpoints/wh_abc123/verify" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

A successful verification reactivates the endpoint and starts the replay. If the endpoint was permanently deactivated after the 12-hour window, re-enable it with PATCH .../{webhook_id} and {"is_active": true} — but note that its queued events were already dead-lettered by then.

Error Responses​

Errors return detail as a message string. See Error Handling for the response shapes.

StatusdetailDescription
400"Invalid webhook URL: ..."The URL is not a reachable public https address
400"Maximum of 10 webhooks per tenant reached"The workspace already has 10 webhooks
400"Cannot activate webhook until it is verified"is_active: true on an unverified webhook
400"Webhook must be verified before sending test events"Test event on an unverified webhook
400"Cannot retry delivery: ..."The delivery can't be retried in its current state, or the webhook is disabled or unverified
401"Invalid or expired API token"Token is invalid or expired
403"Missing required permission(s): ..."Token lacks the required webhooks:* scope
403message stringA field managed by a connected integration was changed on that integration's webhook
404"Webhook endpoint not found", "Delivery not found"Unknown ID, or it belongs to another workspace
422list of field errorsInvalid request body, for example an unknown event type

Next Steps​