Velstack

Webhooks

Webhooks are an important part of your integration. When building Velstack integrations, you might want Velstack to notify your applications about events such as a successful SMS delivery or a failed message as they occur in your accounts, so that your backend systems can execute actions accordingly.

A webhook URL is an endpoint on your server where you can receive notifications about such events. When an event occurs, we'll make a POST request to that endpoint, with a JSON body containing the details about the event, including the type of event and the data associated with it.

Webhooks use cases

In a nutshell, a bulk SMS campaign can be sent successfully but when there are delivery issues, callbacks can fail and you might not get notified about delivery status.

  • When SMS delivery status is updated
  • A pending/scheduled SMS transitions to sent or failed

These are all asynchronous actions, they are not controlled by your application, so you won't know when they are completed, unless we notify you or you check later.

Creating webhook URL

To register a new webhook, you need to setup a URL on your dashboard that Velstack can call. You can configure a new webhook from the Velstack dashboard under API settings . During testing, you can setup an instant URL by visiting webhook.site. This will allow you to inspect the received payload without writing any code or set up a server.

Here is how to setup and configure webhook url in your application.

  • The endpoint you specify on your dashboard must be the same as the endpoint in your application.
  • If the webhook endpoint in your application is using CSRF protection, please disable it.

Creating endpoint

<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">express</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">require</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;express&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">app</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">express</span><span style="color: var(--shiki-color-text)">();</span></span>
<span><span style="color: var(--shiki-token-constant)">app</span><span style="color: var(--shiki-token-function)">.use</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">express</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">());</span></span>
<span></span>
<span><span style="color: var(--shiki-token-constant)">app</span><span style="color: var(--shiki-token-function)">.post</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;/webhook&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-comment)">// Process the webhook payload</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-comment)">// ...</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">200</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">({ message</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;Webhook received&#39;</span><span style="color: var(--shiki-color-text)"> });</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>

Now, whenever any event happens in your on account, a webhook is fired off by Velstack to this endpoint.

If your app doesn't respond with a response code starting with 2XX, Velstack will assume that the event failed. So we will retry sending the webhook after 1 minute. If that second attempt fails, Velstack will attempt to call the webhook a final time after 5 minutes.

Listening to Webhooks

  • When your app receives a webhook request from Velstack, check the event attribute to see what event was sent. The event will tell you the payload type, e.g., sms_sent, sms_failed, etc.

  • Since your webhook URL is publicly accessible, you need to verify that events originate from Velstack and not from a malicious server. This ensures that the event payload has not been tampered with during transmission from Velstack's server.

Validate Signature

Events sent from Velstack include the X-VELSTACK-SIGNATURE in the request header. This header's value is an HMAC SHA256 hash of the event payload, signed using your SECRET_KEY. You should verify the header signature before making any database queries or processing the event.

Validate signature

<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">express</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">require</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;express&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">crypto</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">require</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;crypto&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">app</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">express</span><span style="color: var(--shiki-color-text)">();</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">secret</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;sk_live_rcKGRa3Tagsp6pygEA7bY9oYUEGgYBVqFI8y5nDbc0aa0d7b&quot;</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-constant)">app</span><span style="color: var(--shiki-token-function)">.use</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">express</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">());</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">isValidVelstackCallbackSignature</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> (req) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">```python</span></span>
<span><span style="color: var(--shiki-token-string-expression)">from flask import Flask, request, jsonify</span></span>
<span><span style="color: var(--shiki-token-string-expression)">import hmac</span></span>
<span><span style="color: var(--shiki-token-string-expression)">import hashlib</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">app = Flask(__name__)</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">SECRET = &quot;sk_live_rcKGRa3Tagsp6pygEA7bY9oYUEGgYBVqFI8y5nDbc0aa0d7b&quot;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">def is_valid_velstack_callback_signature(payload, signature):</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    calculated_signature = hmac.new(</span></span>
<span><span style="color: var(--shiki-token-string-expression)">        SECRET.encode(&#39;utf-8&#39;), payload.encode(&#39;utf-8&#39;), hashlib.sha256</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    ).hexdigest()</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    return hmac.compare_digest(calculated_signature, signature)</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">@app.route(&#39;/webhook&#39;, methods=[&#39;POST&#39;])</span></span>
<span><span style="color: var(--shiki-token-string-expression)">def handle_webhook():</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    payload = request.get_data(as_text=True)</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    signature = request.headers.get(&#39;X-VELSTACK-SIGNATURE&#39;)</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">    if is_valid_velstack_callback_signature(payload, signature):</span></span>
<span><span style="color: var(--shiki-token-string-expression)">        app.logger.info(&#39;is valid now &#39; + payload)</span></span>
<span><span style="color: var(--shiki-token-string-expression)">        return jsonify({}), 200</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    else:</span></span>
<span><span style="color: var(--shiki-token-string-expression)">        return &#39;Invalid signature&#39;, 400</span></span>
<span></span>
<span><span style="color: var(--shiki-token-string-expression)">if __name__ == &#39;__main__&#39;:</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    app.run(port=5000)</span></span>
<span></span>
<span></span>

If your generated signature matches the x-velstack-signature header, you can be sure that the request was truly coming from Velstack.


Supported events

<span><span style="color: var(--shiki-color-text)">{</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">&quot;event&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;sms.sent&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">&quot;data&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;id&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">17207978934486</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;status&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;sent&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;reference&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;s7KKcsxXr6j4NSbE&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;phone&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;+233501234567&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;message&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;Your OTP is 1234&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;sms_count&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;message_type&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;transactional&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">&quot;created_at&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;2024-07-12T15:24:53.000000Z&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

Event types

  • Name
    sms.sent
    Type
    Description

    A message was successfully sent to the recipient

  • Name
    sms.failed
    Type
    Description

    A message failed to send