Developers

SMS API reference

Send SMS from your own stack with a single REST endpoint. Authenticate with a bearer token, post JSON, and receive delivery status through webhooks.

Quick Start

Base URL

https://rest.api.fortwave.io/v1

All API requests must be made over HTTPS.

Code Examples

Ready-to-use code snippets for sending SMS in popular programming languages.

const axios = require('axios');

async function sendSMS() {
  const response = await axios.post(
    'https://rest.api.fortwave.io/v1/sms',
    {
      phone_number: '+25761000000',
      sender: 'MyApp',
      message: 'Hello! Your verification code is 123456.'
    },
    {
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }
  );
  console.log(response.data);
}

sendSMS();
import requests

def send_sms():
    response = requests.post(
        'https://rest.api.fortwave.io/v1/sms',
        json={
            'phone_number': '+25761000000',
            'sender': 'MyApp',
            'message': 'Hello! Your verification code is 123456.'
        },
        headers={
            'Authorization': 'Bearer YOUR_API_KEY',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    )
    print(response.json())

send_sms()
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

func sendSMS() {
    payload := map[string]string{
        "phone_number": "+25761000000",
        "sender":       "MyApp",
        "message":      "Hello! Your verification code is 123456.",
    }

    jsonData, _ := json.Marshal(payload)

    req, _ := http.NewRequest("POST",
        "https://rest.api.fortwave.io/v1/sms",
        bytes.NewBuffer(jsonData))

    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    fmt.Println("Status:", resp.Status)
}

func main() {
    sendSMS()
}
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://rest.api.fortwave.io/v1/sms',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'phone_number' => '+25761000000',
        'sender' => 'MyApp',
        'message' => 'Hello! Your verification code is 123456.'
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
        'Accept: application/json'
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
require 'net/http'
require 'json'
require 'uri'

uri = URI('https://rest.api.fortwave.io/v1/sms')

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.body = {
  phone_number: '+25761000000',
  sender: 'MyApp',
  message: 'Hello! Your verification code is 123456.'
}.to_json

response = http.request(request)
puts response.body

Prerequisites

Before you can send an SMS, ensure the following:

  1. API Token β€” Generate an API token for your user account.
  2. Company β€” Your user account must be the creator of a company.
  3. Company Approval β€” Your company must have been approved by an administrator.
  4. Sender ID β€” Register a sender name under your company (1–11 alphanumeric characters).
  5. Sender ID Approval β€” The sender ID must be approved before it can be used to send messages.

Authentication

All requests must include a valid Bearer token in the Authorization header. Unauthenticated requests will receive a 401 Unauthorized response.

Getting Your API Key

  1. Sign in to your FortWave dashboard
  2. Navigate to Settings β†’ API Keys
  3. Click "Generate New API Key"
  4. Copy and securely store your API key
Authorization Header
Authorization: Bearer {your_api_token}

Send SMS

Send a single SMS message to a phone number.

POST/sms

Headers

HeaderValueRequired
AuthorizationBearer {token}Yes
Content-Typeapplication/jsonYes
Acceptapplication/jsonYes

Body Parameters

ParameterTypeRequiredDescription
phone_numberstringYesRecipient's phone number. Must be a valid Burundi (+257) number. Accepted in local or international format (e.g. +25761000000 or 61000000).
senderstringYesSender name (1–11 alphanumeric characters). Must correspond to an approved Sender ID belonging to your company.
messagestringYesMessage body. Maximum 600 characters.

Example Request

curl -X POST https://rest.api.fortwave.io/v1/sms \
  -H "Authorization: Bearer {your_api_token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "phone_number": "+25761000000",
    "sender": "MyApp",
    "message": "Hello! Your verification code is 123456."
  }'

Example Response

200 OK
{
  "id": "01AN4Z07BY79KA1307SR9X4MV3",
  "item_count": 1,
  "remaining_items": 9999,
  "status": "queued",
  "to": "+25769803000",
  "sender": "Fortwave",
  "message": "Message queued for sending"
}

Send Bulk SMS

Queue multiple SMS messages in one request using a single sender and a per-recipient message body. Bulk requests support up to 500 recipients and follow fail-fast behavior: if any business rule fails, no messages are queued and no credits are deducted.

POST/sms/bulk

Headers

HeaderValueRequired
AuthorizationBearer {token}Yes
Content-Typeapplication/jsonYes
Acceptapplication/jsonYes

Body Parameters

ParameterTypeRequiredDescription
senderstringYesSender ID (1-11 alphanumeric characters). Must match an approved sender under your company.
messagesarrayYesArray of recipient payloads. Minimum 1 item, maximum 500 items.
messages[].phone_numberstringYesRecipient number for this item. Must be a valid Burundi (+257) number.
messages[].messagestringYesMessage body for this recipient. Maximum 600 characters.

Bulk endpoint notes

  • Maximum 500 recipients per request.
  • Bulk send does not support the MiraiTest sender. Use the single-message endpoint for test traffic.

Example Request

curl -X POST https://rest.api.fortwave.io/v1/sms/bulk \
  -H "Authorization: Bearer {your_api_token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "sender": "MyApp",
    "messages": [
      { "phone_number": "+25761000000", "message": "Hi Alice" },
      { "phone_number": "+25762000000", "message": "Hi Bob" }
    ]
  }'

Example Response

200 Queued
{
  "batch_id": "01JV5P0Y7B0V3P8NQ2TV6QG5V4",
  "status": "queued",
  "sender": "MyApp",
  "recipients_count": 2,
  "item_count": 2,
  "remaining_items": 498
}

Response Fields

FieldValue
batch_idULID of the created batch record.
statusAlways queued when the request succeeds.
senderSender used for the full batch.
recipients_countNumber of recipients in this batch.
item_countTotal SMS credits consumed by the batch.
remaining_itemsCompany SMS credits remaining after queuing.

Validation Error β€” 422 (Bulk)

Returned for payload shape or field validation errors in sender/messages.

422 Unprocessable Entity
{
  "message": "The given data was invalid.",
  "errors": {
    "messages.0.phone_number": [
      "The messages.0.phone_number field must be a valid Burundi (+257) number."
    ],
    "messages.0.message": [
      "The messages.0.message field must not exceed 600 characters."
    ]
  }
}
FieldMessage
senderThe sender must be 1-11 alphanumeric characters.
messagesThe messages field must contain at least one item.
messagesThe messages field must not contain more than 500 items.
messages[0].phone_numberThe messages.0.phone_number field is required.
messages[0].phone_numberThe messages.0.phone_number field must be a valid Burundi (+257) number.
messages[0].messageThe messages.0.message field must not exceed 600 characters.

Bad Request β€” 400 (Bulk)

Returned for business-rule failures while creating a bulk batch.

ScenarioError Message
User has no companyYou must create a company before sending messages.
MiraiTest sender used on bulk endpointBulk send does not support the MiraiTest sender; use the single-message endpoint for test traffic.
Company is not approvedYour company must be approved before sending messages.
Company is not verifiedYour company must be verified before sending messages.
Sender name not found on companySender not found.
Sender ID exists but is not approvedThe sender must be approved before sending messages.
Recipient country does not match sender telco countryThe phone number country (:phoneCountry) does not match the sender ID's telco country (:telcoCountry).
Company does not have enough SMS creditsInsufficient SMS credits. This batch requires :credits :unit.

Credits and Segments

item_count represents the total SMS segments required by all messages in the batch. Longer or Unicode messages can consume multiple credits.

Rate limit: throttle:developers-sms-send (500 requests per minute per authenticated user).

Bulk Code Examples

Ready-to-use snippets for sending a bulk SMS batch in popular languages.

const axios = require('axios');

async function sendBulkSms() {
  const response = await axios.post(
    'https://rest.api.fortwave.io/v1/sms/bulk',
    {
      sender: 'MyApp',
      messages: [
        { phone_number: '+25761000000', message: 'Hi Alice' },
        { phone_number: '+25762000000', message: 'Hi Bob' }
      ]
    },
    {
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }
  );

  console.log(response.data);
}

sendBulkSms();
import requests

def send_bulk_sms():
    response = requests.post(
        'https://rest.api.fortwave.io/v1/sms/bulk',
        json={
            'sender': 'MyApp',
            'messages': [
                {'phone_number': '+25761000000', 'message': 'Hi Alice'},
                {'phone_number': '+25762000000', 'message': 'Hi Bob'}
            ]
        },
        headers={
            'Authorization': 'Bearer YOUR_API_KEY',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    )
    print(response.json())

send_bulk_sms()
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type BulkMessage struct {
    PhoneNumber string `json:"phone_number"`
    Message     string `json:"message"`
}

type BulkPayload struct {
    Sender   string        `json:"sender"`
    Messages []BulkMessage `json:"messages"`
}

func sendBulkSMS() {
    payload := BulkPayload{
        Sender: "MyApp",
        Messages: []BulkMessage{
            {PhoneNumber: "+25761000000", Message: "Hi Alice"},
            {PhoneNumber: "+25762000000", Message: "Hi Bob"},
        },
    }

    jsonData, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST", "https://rest.api.fortwave.io/v1/sms/bulk", bytes.NewBuffer(jsonData))
    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

func main() {
    sendBulkSMS()
}
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://rest.api.fortwave.io/v1/sms/bulk',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'sender' => 'MyApp',
        'messages' => [
            ['phone_number' => '+25761000000', 'message' => 'Hi Alice'],
            ['phone_number' => '+25762000000', 'message' => 'Hi Bob'],
        ],
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
        'Accept: application/json'
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
require 'net/http'
require 'json'
require 'uri'

uri = URI('https://rest.api.fortwave.io/v1/sms/bulk')

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.body = {
  sender: 'MyApp',
  messages: [
    { phone_number: '+25761000000', message: 'Hi Alice' },
    { phone_number: '+25762000000', message: 'Hi Bob' }
  ]
}.to_json

response = http.request(request)
puts response.body

Send Template SMS

Queue one template message to multiple recipients. Each recipient provides values for template placeholders in a variables object.

POST/sms/template

Headers

HeaderValueRequired
AuthorizationBearer {token}Yes
Content-Typeapplication/jsonYes
Acceptapplication/jsonYes

Body Parameters

ParameterTypeRequiredDescription
senderstringYesSender ID (1-11 alphanumeric characters). Must match an approved sender under your company.
templatestringYesTemplate text with placeholders. Maximum 600 characters.
recipientsarrayYesArray of recipient payloads. Minimum 1 item, maximum 500 items.
recipients[].phone_numberstringYesRecipient number for this item. Must be a valid Burundi (+257) number.
recipients[].variablesobjectYesKey/value object for placeholder replacements for this recipient.
recipients[].variables.*string | nullYesVariable value for each placeholder key. Nullable string, maximum 255 characters.

Template endpoint notes

  • Maximum 500 recipients per request.
  • If any recipient misses a required placeholder variable, the entire request fails.
  • When placeholder validation fails, no messages are queued and no credits are deducted.
  • Extra keys in recipients[].variables are ignored.
  • If a variable value is null, it is rendered as an empty string.
  • Template bulk send does not support the MiraiTest sender. Use the single-message endpoint for test traffic.

Placeholder Rules

  • Use double curly braces in the template for named variables such as name and code.
  • Variable names must start with a letter or underscore, then contain letters, numbers, or underscores.
  • Whitespace inside placeholder braces is supported.

Example Request

curl -X POST https://rest.api.fortwave.io/v1/sms/template \
  -H "Authorization: Bearer {your_api_token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "sender": "MyApp",
    "template": "Hello {{name}}, your code is {{code}}.",
    "recipients": [
      {
        "phone_number": "+25761000000",
        "variables": { "name": "Alice", "code": "1234" }
      },
      {
        "phone_number": "+25762000000",
        "variables": { "name": "Bob", "code": "5678" }
      }
    ]
  }'

Example Response

200 Queued
{
  "batch_id": "01JS9M8AN9T25FE0J8QWWHE6BV",
  "status": "queued",
  "sender": "MyApp",
  "recipients_count": 2,
  "item_count": 2,
  "remaining_items": 498
}

Response Fields

FieldValue
batch_idULID of the created batch record.
statusAlways queued when the request succeeds.
senderSender used for the full batch.
recipients_countNumber of recipients in this batch.
item_countTotal SMS credits consumed by the batch.
remaining_itemsCompany SMS credits remaining after queuing.

Validation Error β€” 422 (Template)

Returned for payload shape or field validation errors in sender/template/recipients.

422 Unprocessable Entity
{
  "message": "The given data was invalid.",
  "errors": {
    "template": [
      "The template field is required."
    ],
    "recipients.0.variables.code": [
      "The recipients.0.variables.code field must not exceed 255 characters."
    ]
  }
}
FieldMessage
senderThe sender must be 1-11 alphanumeric characters.
templateThe template field is required.
templateThe template field must not exceed 600 characters.
recipientsThe recipients field must contain at least one item.
recipientsThe recipients field must not contain more than 500 items.
recipients[0].phone_numberThe recipients.0.phone_number field is required.
recipients[0].phone_numberThe recipients.0.phone_number field must be a valid Burundi (+257) number.
recipients[0].variablesThe recipients.0.variables field is required.
recipients[0].variables.codeThe recipients.0.variables.code field must not exceed 255 characters.

Bad Request β€” 400 (Template)

Returned for business-rule failures while creating a template batch.

ScenarioError Message
User has no companyYou must create a company before sending messages.
MiraiTest sender used on template endpointBulk send does not support the MiraiTest sender; use the single-message endpoint for test traffic.
Company is not approvedYour company must be approved before sending messages.
Company is not verifiedYour company must be verified before sending messages.
Sender name not found on companySender not found.
Sender ID exists but is not approvedThe sender must be approved before sending messages.
Recipient country does not match sender telco countryThe phone number country (:phoneCountry) does not match the sender ID's telco country (:telcoCountry).
Company does not have enough SMS creditsInsufficient SMS credits. This batch requires :credits :unit.
A recipient is missing required template variablesRecipient #1 is missing required template variables: code.

Credits and Segments

item_count represents the total SMS segments required by all rendered messages in the batch. Longer or Unicode messages can consume multiple credits.

Rate limit: throttle:developers-sms-send (500 requests per minute per authenticated user).

Template SMS Code Examples

Ready-to-use snippets for sending a template SMS batch in popular languages.

const axios = require('axios');

async function sendTemplateSms() {
  const response = await axios.post(
    'https://rest.api.fortwave.io/v1/sms/template',
    {
      sender: 'MyApp',
      template: 'Hello {{name}}, your code is {{code}}.',
      recipients: [
        {
          phone_number: '+25761000000',
          variables: { name: 'Alice', code: '1234' }
        },
        {
          phone_number: '+25762000000',
          variables: { name: 'Bob', code: '5678' }
        }
      ]
    },
    {
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }
  );

  console.log(response.data);
}

sendTemplateSms();
import requests

def send_template_sms():
    response = requests.post(
        'https://rest.api.fortwave.io/v1/sms/template',
        json={
            'sender': 'MyApp',
            'template': 'Hello {{name}}, your code is {{code}}.',
            'recipients': [
                {
                    'phone_number': '+25761000000',
                    'variables': {'name': 'Alice', 'code': '1234'}
                },
                {
                    'phone_number': '+25762000000',
                    'variables': {'name': 'Bob', 'code': '5678'}
                }
            ]
        },
        headers={
            'Authorization': 'Bearer YOUR_API_KEY',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    )
    print(response.json())

send_template_sms()
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type TemplateRecipient struct {
    PhoneNumber string            `json:"phone_number"`
    Variables   map[string]string `json:"variables"`
}

type TemplatePayload struct {
    Sender     string              `json:"sender"`
    Template   string              `json:"template"`
    Recipients []TemplateRecipient `json:"recipients"`
}

func sendTemplateSMS() {
    payload := TemplatePayload{
        Sender:   "MyApp",
        Template: "Hello {{name}}, your code is {{code}}.",
        Recipients: []TemplateRecipient{
            {
                PhoneNumber: "+25761000000",
                Variables: map[string]string{
                    "name": "Alice",
                    "code": "1234",
                },
            },
            {
                PhoneNumber: "+25762000000",
                Variables: map[string]string{
                    "name": "Bob",
                    "code": "5678",
                },
            },
        },
    }

    jsonData, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST", "https://rest.api.fortwave.io/v1/sms/template", bytes.NewBuffer(jsonData))
    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

func main() {
    sendTemplateSMS()
}
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://rest.api.fortwave.io/v1/sms/template',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'sender' => 'MyApp',
        'template' => 'Hello {{name}}, your code is {{code}}.',
        'recipients' => [
            [
                'phone_number' => '+25761000000',
                'variables' => ['name' => 'Alice', 'code' => '1234'],
            ],
            [
                'phone_number' => '+25762000000',
                'variables' => ['name' => 'Bob', 'code' => '5678'],
            ],
        ],
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
        'Accept: application/json'
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
require 'net/http'
require 'json'
require 'uri'

uri = URI('https://rest.api.fortwave.io/v1/sms/template')

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.body = {
  sender: 'MyApp',
  template: 'Hello {{name}}, your code is {{code}}.',
  recipients: [
    {
      phone_number: '+25761000000',
      variables: { name: 'Alice', code: '1234' }
    },
    {
      phone_number: '+25762000000',
      variables: { name: 'Bob', code: '5678' }
    }
  ]
}.to_json

response = http.request(request)
puts response.body

Test environment

FortWave provides a controlled way to exercise the same SMS API against a dedicated test sender and your own verified test handset. Test sends follow the same POST /sms contract as production, with the rules below.

  • Company. You must create a company before you can send test messages through the API. Company verification by an administrator is not required for test sends.
  • Verified test phone number. Register one Burundi test number under SMS Configurations, then complete SMS verification (6-digit OTP). Only that verified line is eligible to receive test API messages. Verification SMS for setup does not consume your test message quota.
  • Test sender name. Use the fixed sender MiraiTest in the sender field for test sends. It is separate from your registered production sender IDs.
  • Quotas and changes. Test traffic draws from a small per-account pool of SMS segments (not your standard company SMS balance). After your number is verified, you may only replace it a limited number of timesβ€”each change clears verification until you confirm the new number again.

Typical limits (subject to platform policy)

LimitDetails
Test SMS segmentsSmall fixed pool per account (e.g. five segments); counts test API deliveries, not OTP setup messages.
Verified number updatesLimited number of times you may save a different test number after the previous one was verified.

Manage your test number and OTP flow in the dashboard: SMS Configurations

Error Responses

Validation Error β€” 422

Returned when one or more fields fail validation.

422 Unprocessable Entity
{
  "message": "The phone number must be a valid Burundi (+257) number.",
  "errors": {
    "phone_number": [
      "The phone number must be a valid Burundi (+257) number."
    ]
  }
}
FieldMessage
phone_numberThe phone number is required.
phone_numberThe phone number must be a valid Burundi (+257) number.
senderThe sender is required.
senderThe sender must be 1–11 alphanumeric characters.
messageThe message content is required.
messageThe message must not exceed 600 characters.

Bad Request β€” 400

Returned when a business rule prevents message sending.

ScenarioError Message
User has no companyYou must create a company before sending messages.
Company is not approvedYour company must be approved before sending messages.
Sender name not found on companySender not found.
Sender ID exists but is not approvedThe sender must be approved before sending messages.
400 Bad Request
{
  "message": "Your company must be approved before sending messages."
}

Unauthorized β€” 401

401 Unauthorized
{
  "message": "Unauthenticated."
}

Message Lifecycle

Once a message is accepted, it progresses through the following statuses:

StatusDescription
pendingMessage has been created and queued for delivery to the carrier.
sentCarrier has accepted and sent the message.
failedMessage delivery failed at the carrier level.

The phone number is stored in E.164 format (e.g. +25761000000) regardless of the input format provided.

Webhooks

If your company has an active webhook endpoint configured, FortWave will send real-time status notifications as your message progresses. Each webhook request includes the following custom header:

HeaderDescription
X-Webhook-EventThe event name (e.g. sms.sent, sms.failed).

Supported Events

EventTriggered When
sms.sentThe carrier confirms the message was sent.
sms.failedThe carrier reports a delivery failure.

Webhook Payload

Webhook payloads are sent as POST requests with a Content-Type: application/json body:

Webhook Payload
{
  "event": "sms.sent",
  "timestamp": "2026-03-26T12:00:00+00:00",
  "data": {
    "message_id": 42,
    "phone_number": "+25761000000",
    "status": "sms.sent",
    "sent_at": "2026-03-26T12:00:00+00:00",
    "failed_at": null
  }
}

Deliveries Retry Policy

Failed webhook deliveries are retried up to 5 times with exponential backoff. After all retries are exhausted, the delivery is marked as permanently failed and no further retries are attempted.

AttemptDelay
110 seconds
230 seconds
360 seconds
45 minutes
515 minutes