Webhooks

Learn how to use webhooks to automate actions based on specified events.

Overview

Webhooks are automated messages sent by Truv. These notifications are HTTP POST requests to a predefined URL when an event occurs on the platform.

Use webhooks to track Task status changes during processing stages. Successful payroll connections take time to process data and PDF documents from the payroll provider. Webhooks provide updates for status changes and maintain transparency for the user experience.

Subscribing

Visit the Truv Dashboard Development > Webhooks and add a URL for your environment. Each environment requires a specified separate URL.

Testing

Test webhooks with the Truv Bridge in the Truv Emulator. For immediate use, ngrok and MockBin lets you test payloads in either your local enviornment or your browser, respectively.

Payload

Many fields are available when Truv sends a request to a webhook endpoint. Quite a few depend on the event that is being sent. Here's a list of the common fields that occur in every call regardless of the event:

NameInDescription
user-agentheaderAlways set to Truv-Webhook-Client/2.0
x-webhook-signheaderHash of created request body created with Access secret
webhook_idbodyUnique ID for specific webhook request
event_typebodyID of sent webhook event request
updated_atbodyTime event occurred
user_idbodyUnique ID of user

๐Ÿ“˜

Note

All header field names are case-insensitive. For more information, refer to HTTP/1.1 Specifications, Section 3.2.

Timing

Truv processes webhooks requests for Task statuses in their updated order. For example, full_parse events must happen before done events. Webhook request deliveries may experience delays due to things outside of Truv, such as network latency, outages, and other external issues.

Configure your webhook integrations to track the updated_at field for monitoring events in sequence.

Security

Webhook signatures confirm and verify Truv data for you. Each request contains the X-WEBHOOK-SIGN value within the header. This hash-based message authentication code (HMAC) uses the Access secret as the key and SHA-256 as the function.

Verification steps

The steps in the section below guide you through verifying requests from Truv.

  1. Use a hash library to create an HMAC hash of the raw request body received by Truv. Use the Access secret or access_token from the X-Access-Secret header as the hashing key. Confirm the SHA-256 as the hash function and the final hash has hexadecimal conversion.

  2. Compare the hash to the value in the X-WEBHOOK-SIGN header from the webhook request. Truv sends requests for matching values, and you can continue to process the webhook.

import hashlib
import hmac

def generate_webhook_sign(payload: str, key: str) -> str:
    generated_hash = hmac.new(
        key=key.encode('utf-8'),
        msg=payload.encode('utf-8'),
        digestmod=hashlib.sha256,
    ).hexdigest()
    return f'v1={generated_hash}'

@app.route('/webhook', methods=['POST'])
def webhook():
    return generate_webhook_sign(request.data.decode('UTF-8'), secret)
import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "fmt"
  "strings"
)

func generate_webhook_sign(body string, key string) string {

  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(body))
  return hex.EncodeToString(mac.Sum(nil))
}

func webhook(w http.ResponseWriter, r *http.Request) {
  b, _ := ioutil.ReadAll(r.Body)
  convertedBody := string(b)
  signature := generate_webhook_sign(convertedBody, os.Getenv("API_SECRET"))
  fullSignature := fmt.Sprintf("v1=%s", signature)

  fmt.Fprintf(w, fullSignature)
}
const crypto = require("crypto")

// ensure all request bodies are parsed to JSON. Callback function
// keeps a copy of the raw body for webhooks.
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf
  }
}))

const generate_webhook_sign = (body, key) => {
  return crypto.createHmac("sha256", key)
  .update(body)
  .digest("hex")
}

app.post("/webhook", async (req, res) => {

  const body = req.rawBody.toString()
  const webhook_sign = generate_webhook_sign(body, API_SECRET)

  res.send(`v1=${webhook_sign}`).end()
})
class Webhook
  def self.generate_webhook_sign(body, key)
    digest = OpenSSL::Digest.new('sha256')
    return "v1=" + OpenSSL::HMAC.hexdigest(digest, key, body)
  end

  def self.post(body)
    return self.generate_webhook_sign(body, Citadel.client_secret)
  end
end
using System.Threading.Tasks;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Security.Cryptography;
using System;

namespace c_sharp.Controllers
{
  [ApiController]
  [Route("webhook")]
  public class WebhookController : ControllerBase
  {

    [HttpPost]
    public async Task<string> Post()
    {
      using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
      {
        string body = await reader.ReadToEndAsync();
        return generateWebhookSign(body, Environment.GetEnvironmentVariable("API_SECRET"));
      }
    }

    private string generateWebhookSign(string body, string key)
    {
      using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
      {
        // Compute the hash of the input file.
        byte[] hashValue = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));

        return "v1=" + BitConverter.ToString(hashValue).Replace("-", "").ToLower();

      }
    }
  }
}

HTTP timeouts and retries

HTTP request timeout have strict limits. Unsuccessful requests are limited to three attempts and a 30 second window between each retry. Additional unsuccessful retries results in failed requests and further attempts are blocked.

Status codes

The list below contains information for webhook status codes.

  • Endpoints must return a successful 2xx status code within 10 seconds of a request
  • Redirects signaled with 3xx status codes are valid with Truv
  • Any 4xx and 5xx response status codes result as an error

Webhook event descriptions

Find all of the webhooks reference information on the Webhook Event Descriptions page. The sample content below covers payloads from changes to Tasks.

Task status change

This sample webhook is the payload for task-status-updated.

{
   "webhook_id":"488f424096d3461aa0e4cf11a985f6df",
   "task_id":"521442a087604e599c637db310d07402",
   "link_id":"d39d86dcc20c46e0ae75ba9ab1311a21",
   "product":"income",
   "tracking_info":null,
   "event_type":"task-status-updated",
   "event_created_at":"2022-08-24T13:55:12.845645Z",
   "updated_at":"2022-08-24T13:55:12.845666+00:00",
   "status":"parse",
   "user_id": "88fef4cea64c40b5ad6727cc9b0b9fdc"
}

The task-status-updated event occurs when the status of a Task changes. When receiving a task-status-updatedevent with a status of done, all data from the Task is downloaded and document processing is complete.

Use the link_id value to locate the access_token in your system. This allow you to retrieve the latest payroll data from the respective endpoint. View more information about the fields in the list below.

  • task_id - ID of Task associated with event
  • link_id - ID of Link associated to Task
  • product - Product specified for Task, employment, income or admin
  • tracking_info - Info passed to bridge_token for Link , nullable
  • status - Connection Lifecycle status update