Webhooks

Webhooks are automated notifications to defined URLs that inform about events in real time. They are useful because they enable an immediate response to specific events without the need for continuous polling. Webhooks help keep systems efficient and responsive.

Your webhooks are called immediately when an event occurs. If we cannot reach your URL or it responds with an error status, we retry the delivery on a fixed schedule and additionally monitor over several days whether the receiver is permanently failing. The full behavior is documented under Delivery & retries.


Delivery & retries

When we retry a webhook

A webhook is only re-delivered when the response from your server indicates a temporary failure:

  • Name
    HTTP 429
    Type
    rate limit
    Description

    Your endpoint signals that it is currently receiving too many requests.

  • Name
    HTTP 5xx
    Type
    server error
    Description

    Specifically 500, 502, 503 and 504, plus the Cloudflare origin error codes 520 to 524 (e.g. 521 "Web Server Is Down", which Cloudflare returns instead of 502 for proxied domains).

  • Name
    Connection errors
    Type
    network
    Description

    Timeouts, DNS resolution failures, TLS/handshake errors or a connection that is dropped during the request.

All other responses are treated as final and do not trigger another delivery attempt. This includes 4xx codes such as 400, 401, 403 and 404, since these usually point to a configuration issue on the receiver side that cannot be fixed by simply retrying. Responses in the 2xx and 3xx range count as successful.

Request timeout

Each individual delivery attempt has a timeout of 5 seconds. If your server does not respond within this window we treat the attempt as failed and schedule a retry according to the plan below.

Retry schedule

If an attempt fails, the next one is scheduled after a fixed, increasing delay. We make a total of up to 14 attempts; after that the job is dropped. The whole delivery window therefore spans roughly 3.5 days.

AttemptDelay since previous attempt
1 (initial)immediately
21 minute
35 minutes
430 minutes
560 minutes
62 hours
7 to 1410 hours each

Every attempt is signed separately and carries fresh X-Timestamp, X-Nonce and X-Signature headers, see Check signature below.

Automatic disabling on persistent failures

In addition to per-message retries, we continuously monitor the daily failure rate per webhook. A webhook is flagged as "problematic" once, on a given day,

  • the daily failure rate is above 80 %, and
  • at least 10 failures have occurred that day.

From that point on the webhook is observed over several days. If the situation does not improve, the system escalates step by step:

DayThreshold (rolling failure rate)Action
1above 80 %Warning email level 1
3above 85 %Warning email level 2
5above 90 %Warning email level 3
7above 95 %Webhook is automatically disabled, audit-log entry written and disable email

Day counters start from the day the problem is first detected.

After auto-disable

A webhook that has been disabled automatically stays inactive until you re-enable it manually in the dashboard. After re-activation the statistics start fresh.

What counts as "successful"?

For the failure-rate monitor, every response that is not a retry trigger counts as "ok". In particular, 2xx, 3xx and even 4xx responses (except 429) are treated as successful, because from the delivery system's perspective they are an unambiguous, final answer.


Security

Check source IP

All webhooks originate from our IPv4 195.201.160.143 or from the IPv6 2a01:4f8:13a:8e7::2. Should this change, you will find all further details in our Changelog.

Check signature

All webhooks to your server are signed with the signing key of your account. You can use this data to validate the authenticity of the requests from us for each webhook received and avoid duplication or fraudulent requests. To do this, check the signature as described on the Signing page.

We send you the following headers in every webhook:

  • Name
    X-Signature
    Type
    string
    Description

    The signature generated by us

  • Name
    X-Timestamp
    Type
    integer
    Description

    Timestamp at which the signature was created by us

  • Name
    X-Nonce
    Type
    string
    Description

    Randomly generated string with 32 characters

Every single delivery attempt is signed separately. The initial delivery, every automatic retry from the retry schedule and a manual resend from the webhook debugger each carry their own X-Timestamp, their own X-Nonce and a matching X-Signature. A retry that arrives hours after the event therefore still carries a current timestamp and a nonce that has never been used before.

For this reason the 30 second timestamp window and the nonce uniqueness described under Signing also apply to retries. Keep your validation window at 30 seconds and do not widen it for retries.


Examples as JSON payload

SMS

Incoming SMS (sms_mo)

{
  "data": {
    "id": "681590",
    "sender": "SMS",
    "system": "491771783130",
    "text": "Hello World",
    "time": "1605878104",
    "message_type": "SMS"
  },
  "webhook_event": "sms_mo",
  "webhook_timestamp": "2020-12-02 11:55:44"
}

SMS status change (dlr)

{
  "data": {
    "msg_id": "77149843739",
    "status": "TRANSMITTED",
    "timestamp": "2021-08-24 08:08:00.000000"
  },
  "webhook_event": "dlr",
  "webhook_timestamp": "2021-08-24T08:08:00+02:00"
}

Performance Tracking (tracking)

{
    "webhook_event": "tracking",
    "webhook_timestamp": "2022-07-27T07:38:18+02:00",
    "data": {
        "sms_id": "77182424125",
        "sms_label": null,
        "tracking_url": "https://svn.me/7oz",
        "final_url": "https://www.google.com",
        "type": "click",
        "total_clicks": 5,
        "total_views": 3
    }
}

Voice

Voice message status change (voice_status)

{
  "data": {
    "callerId": "49176123456789",
    "duration": "4",
    "id": "284195",
    "pricePerMinute": 0.075,
    "recipient": "4943160049851",
    "status": "completed",
    "timestamp": 1629786769
  },
  "webhook_event": "voice_status",
  "webhook_timestamp": "2021-08-24T08:32:50+02:00"
}

Incoming voice call (voice_call)

{
  "webhook_event": "voice_call",
  "webhook_timestamp": "2024-08-02T07:28:59+02:00",
  "data": {
    "id": 0,
    "caller": "4943160049851",
    "time": 1722576539,
    "system": "4915170517246"
  }
}

DTMF signal (voice_dtmf)

{
  "webhook_event": "voice_dtmf",
  "webhook_timestamp": "2024-08-02T07:28:59+02:00",
  "data": {
    "id": 0,
    "callerId": "4943160049851",
    "recipient": "4943160049851",
    "status": "completed",
    "system": "4915170517246",
    "timestamp": 1722576539,
    "duration": 2.76,
    "pricePerMinute": 0.045,
    "dtmf_digit": 9,
    "total_price": 0.045
  }
}

RCS

Message has been delivered

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T10:01:07+01:00",
  "data":
  {
    "msg_id": "77233699836",
    "status": "DELIVERED",
    "timestamp": "1709888466.254410",
    "foreign_id": null,
    "agent_id": "myfancyagent"
  }
}

Message has been read

{
    "webhook_event": "rcs",
    "webhook_timestamp": "2024-03-08T10:01:09+01:00",
    "data":
    {
      "msg_id": "77233699836",
      "status": "READ",
      "timestamp": "1709888468.065783",
      "foreign_id": null,
      "agent_id": "myfancyagent"
    }
  }

Status report of a sent message

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T10:00:18+01:00",
  "data":
  {
    "sender": "4915153952979",
    "status": "IS_TYPING",
    "agent_id": "myfancyagent"
  }
}

A text message has been sent

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T09:47:12+01:00",
  "data": {
    "id": 1871353,
    "sender": "4915153952979",
    "time": "1709870553",
    "message_type": "RCS",
    "content_type": "text",
    "text": "Hello",
    "agent_id": "myfancyagent"
  }
}

A suggested answer has been selected

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T05:59:33+01:00",
  "data": {
    "id": 1870983,
    "sender": "4915153952979",
    "time": "1709870129",
    "message_type": "RCS",
    "content_type": "suggestion_response",
    "suggestion_response": {
      "postbackData": "suggestion_2",
      "text": "Suggestion #2",
      "type": "REPLY"
    },
    "agent_id": "myfancyagent"
  }
}

An image has been sent

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T08:58:20+01:00",
  "data": {
    "id": 1871195,
    "sender": "4915153952979",
    "time": "1709870556",
    "message_type": "RCS",
    "content_type": "user_file",
    "user_file": {
      "thumbnail": {
        "mimeType": "image/jpeg",
        "fileSizeBytes": 10166,
        "fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f36357145416a773452537737696e495a366a68454a42344639574937716e62704156765358664d486e42776730337334675a76522b6d4f574b6e32626632546842493d.jpeg"
      },
      "payload": {
        "mimeType": "image/jpeg",
        "fileSizeBytes": 611314,
        "fileName": "IMG_20240308_050231_01.jpg",
        "fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f76746e45515768746866717653444e6166336a513852375249614d753637656f414a6f4758624d5358417269306e767331493645372b47446654325a66764b3168453d.jpeg"
      }
    },
    "agent_id": "myfancyagent"
  }
}

A file has been sent

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T11:07:51+01:00",
  "data": {
    "id": 1871485,
    "sender": "4915153952979",
    "time": "1709892233",
    "message_type": "RCS",
    "content_type": "user_file",
    "user_file": {
      "payload": {
        "mimeType": "application/pdf",
        "fileSizeBytes": 18810,
        "fileName": "sample (1).pdf",
        "fileUri": "https://static.seven.io/uploads/rbm/61513d3d/617678715267303d/4f616c675256696b73524c6c75337a4b5a66336a51705570464e665a752f6d4e70464d37546e504b5458423730556a7337314e70514c3241436650304d616e4b3078493d.pdf"
      }
    },
    "agent_id": "myfancyagent"
  }
}

A location has been shared

{
  "webhook_event": "rcs",
  "webhook_timestamp": "2024-03-08T11:07:51+01:00",
  "data": {
    "id": 1871485,
    "sender": "4915153952979",
    "time": 1709892233,
    "message_type": "RCS",
    "content_type": "location",
    "location": {
        "latitude": 54.3216562,
        "longitude": 10.1350767
    },
    "agent_id": "myfancyagent"
  }
}

WhatsApp

Incoming WhatsApp messages are delivered with the event wa_mo. The data object always carries the same base fields, regardless of what the customer sent:

  • Name
    id
    Type
    integer
    Description

    Internal ID of the inbound message

  • Name
    sender
    Type
    string
    Description

    Phone number of the sender in international format without a leading plus

  • Name
    time
    Type
    integer
    Description

    Unix timestamp of when the message was received

  • Name
    text
    Type
    string
    Description

    Message content. Plain text for text messages, a JSON encoded string for every other content type

  • Name
    message_type
    Type
    string
    Description

    Always WA

  • Name
    system
    Type
    string
    Description

    Your WhatsApp Business number the message was sent to

If you set an event filter on the webhook, it is matched against system, so you can route messages per WhatsApp Business number.

Incoming text message (wa_mo)

{
  "webhook_event": "wa_mo",
  "webhook_timestamp": "2026-08-21T14:50:12+02:00",
  "data": {
    "id": 4521987,
    "sender": "353861234567",
    "time": 1755780612,
    "text": "Hello World",
    "message_type": "WA",
    "system": "4915170517246"
  }
}

Content types other than plain text

Media, locations, contacts, quick reply buttons and interactive replies are delivered in the same envelope. The text field then contains a JSON encoded string whose type key tells you what was received:

typeSent by the customer
user_fileImage, video, audio, document or sticker
locationA shared location
contactA shared contact card
buttonQuick reply button of a template message
interactiveReply to an interactive message (button or list reply)

Decoded contents of the text field:

Media (user_file)

{
  "type": "user_file",
  "user_file": [
    {
      "type": "image",
      "mediaId": "1234567890123456",
      "mimeType": "image/jpeg",
      "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "fileName": "IMG_20260821_145012.jpg",
      "fileUri": "https://static.seven.io/uploads/whatsapp/61513d3d/6176746a/4f36357145416a773452537737/IMG_20260821_145012.jpg",
      "caption": "Look at this"
    }
  ]
}

type is derived from the MIME type and is one of image, video, audio, document or sticker. If we cannot download the file from Meta, fileUri is missing and the object carries "downloadFailed": true instead - in that case use mediaId to fetch the file yourself.

Location

{
  "type": "location",
  "location": {
    "latitude": 54.3216562,
    "longitude": 10.1350767,
    "name": "seven communications GmbH",
    "address": "Alte Weide 7, 24116 Kiel"
  }
}

Contact

{
  "type": "contact",
  "contact": {
    "formattedName": "John Doe",
    "firstName": "John",
    "lastName": "Doe",
    "middleName": null,
    "phones": [{"phone": "+353861234567", "type": "CELL"}],
    "emails": [{"email": "john@example.com", "type": "WORK"}],
    "addresses": [],
    "company": "Example Ltd",
    "birthday": "1985-04-12"
  }
}

Quick reply button

{
  "type": "button",
  "button": {
    "text": "Confirm appointment",
    "payload": "confirm_2026_08_21"
  }
}

Interactive reply

{
  "type": "interactive",
  "interactive": {
    "item_id": "option_2",
    "title": "Option #2",
    "description": "Delivery on Friday"
  }
}
Last updated: 7 hours ago