# Receive status updates

Sending an SMS is an asynchronous operation by nature. When we are sending an SMS to the Telecom Provider on your behalf, they acknowledge the message then schedule it for delivery as soon as possible.

For example the mobile headset may not have service at the specific time you're sending the SMS.

So in order to receive status changes about an SMS after it has been sent, you need a way to receive events generated by our system. Enters webhooks.


# How does it work ?

Webhooks are simply an api call that our system will make when it needs to notify you about a specific event

sequenceDiagram
	autonumber
    participant Your system
    participant Sofy
    Your system->>Sofy: POST /sms
    Sofy->>Your system: "pending" status
	Note left of Sofy: Reply immediately with and ID for this SMS
	loop
        Sofy-->Sofy: Process the message
    end
	alt Couldn't not process
		Sofy->>Your system: "errored" status
	end
	Sofy->>Telecom Provider: Send SMS
	Sofy->>Your system: "sent" status
	loop SMS Center
		Telecom Provider-->Telecom Provider: Deliver SMS to headset
	end
	Telecom Provider-->>Sofy: Sms status update
	Sofy->>Your system: Notify via Webhook

# Register a webhook

You can create an api endpoint on your system or use a no-code solution to receive webhooks. You need to have an api url to provide when creating a webhook resource. Once created, the webhook will receive all further status callbacks. You can unregister your webhook at a later point if your url has changed for example.

You can then create a Webhook by calling the /webhooks endpoint.

For example if your url is https://yourdomain.com/api/sofyStatus

curl -X 'POST' \
  'https://api.sofy.fr/v1/webhooks' \
  -H 'accept: application/json' \
  -H 'X-API-KEY-ID: xxxxx' \
  -H 'X-API-KEY-SECRET: yyyyy' \
  -H 'Content-Type: application/json' \
  -d '{
	"url": "https://yourdomain.com/api/sofyStatus",
	"events": ["sms.status"]
  }'
import axios from 'axios';

axios.post(
  'https://api.sofy.fr/v1/webhooks',
  {
    url: 'https://yourdomain.com/api/sofyStatus',
    events: ['sms.status'],
  },
  {
    headers: {
      'X-API-KEY-ID': 'xxxxx',
      'X-API-KEY-SECRET': 'yyyyy',
    },
  },
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.sofy.fr/v1/webhooks");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "X-API-KEY-ID: " . $ID,
    "X-API-KEY-SECRET: " . $SECRET,
	"Content-Type: application/json"
));

$payload = json_encode(array("events" => array('sms.status'), "url" => "https://myapiurl.com/statuses"));

echo $payload;
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

echo $response;

# Examples

SMS sent webhook
{
  "data": [
    {
      "body": "Hello world !",
      "from": "36789",
      "to": "590690112233",
      "createdAt": "2022-12-29T18:24:27.000Z",
      "updatedAt": "2022-12-29T18:24:44.938Z",
      "id": "zzzzz",
      "status": "sent",
      "statusReason": "",
      "price": 0.08,
      "parts": 1
    }
  ],
  "webhookId": "xxxxx",
  "id": "yyyyy",
  "type": "sms.status"
}
SMS delivered webhook
{
  "data": [
    {
      "body": "Hello world !",
      "from": "36789",
      "to": "590690112233",
      "createdAt": "2022-12-29T18:24:27.000Z",
      "updatedAt": "2022-12-29T18:24:44.938Z",
      "id": "zzzzz",
      "status": "delivered",
      "statusReason": "DELIVRD",
      "price": 0.08,
      "parts": 1
    }
  ],
  "webhookId": "xxxxx",
  "id": "yyyyy",
  "type": "sms.status"
}
SMS errored webhook
{
  "data": [
    {
      "body": "Hello world !",
      "from": "36789",
      "to": "590690112233",
      "createdAt": "2022-12-29T18:24:27.000Z",
      "updatedAt": "2022-12-29T18:24:44.938Z",
      "id": "zzzzz",
      "status": "errored",
      "statusReason": "EXPIRED",
      "price": 0.08,
      "parts": 1
    }
  ],
  "webhookId": "xxxxx",
  "id": "yyyyy",
  "type": "sms.status"
}
No credit webhook
{
  "data": [
    {
      "body": "Hello world !",
      "from": "36789",
      "to": "590690112233",
      "createdAt": "2022-12-29T18:24:27.000Z",
      "updatedAt": "2022-12-29T18:24:44.938Z",
      "id": "zzzzz",
      "status": "errored",
      "statusReason": "insufficient_balance",
      "price": 0,
      "parts": 1
    }
  ],
  "webhookId": "xxxxx",
  "id": "yyyyy",
  "type": "sms.status"
}
Invalid phone number webhook
{
  "data": [
    {
      "body": "Hello world !",
      "from": "36789",
      "to": "590690112233",
      "createdAt": "2022-12-29T18:24:27.000Z",
      "updatedAt": "2022-12-29T18:24:44.938Z",
      "id": "zzzzz",
      "status": "errored",
      "statusReason": "invalid_phone_number",
      "price": 0,
      "parts": 1
    }
  ],
  "webhookId": "xxxxx",
  "id": "yyyyy",
  "type": "sms.status"
}

In a typical scenario, you will receive 2 webhooks. One when your message is sent, another one to notify you about the status of the SMS: delivered, errored

# Webhooks replay

When called, your webhook endpoint must return a 2xx status to be considered successful. Failed webhooks will be replayed following an exponential backoff if for some reason your system couldn't handle the request at a specific time.

Replays are delayed by at least the following durations:

Failed attempt number Backoff duration
1 1 minute
2 16 minutes
3 1h21m
4 4h16m
5 10h25m
6 21h36m
7 1d 16h01m
8 2d 20h16m
9 4d 13h21m
10 6d 22h40m

If even after the tenth retry, your system still couldn't handle the webhook, Sofy will discard the operation.