Why use webhooks?
Simply put....to save you time and reduce errors and omissions.
Let’s say you’ve registered to receive the toolkit-monitoring-report.clientreadygenerated
event and an advisor generates a client ready monitoring report via a Client or the Batch Reporting interface. By setting up a webhook, you can tell your other systems such as Email, CRM, Document Storage, Messengers, and many others to do something when that monitoring report event is received. This effectively connects your existing systems with the Toolkit in a highly automated and flexible way.
One of the most common examples is sending an email from Outlook to your client that includes the PDF report and a summary of the action items for the investments within their account. Another great example is storing the monitoring report within the client's folder in your OneDrive, Box, Salesforce or other document management or CRM system.
Check out the following video to get some more inspiration!
What if I don't have any technology staff to help set this up?
Don't worry! There are ways to take advantage of this capability without writing a piece of code yourself. Many third-party software tools exist which allow you to do this in a plug and play interface. Two of the most common examples are Microsoft PowerAutomate and Zapier. We have created a number of ready to use templates that can be imported into those tools for even quicker setup. You can use them as is or easily configure them within those tools to meet your desired use case.
Events overview
Events are our way of letting you know when something interesting happens in your account. When an event occurs, we create a new Event object. If you registered a webhook endpoint to receive that event, we send it to your endpoint as part of a POST request.
Example event
{ "EventId": "eda48046-6880-4935-b13e-402f5b7fce18", "EventDate": "2024-01-24T05:01:42.473", "EventType": "toolkit-monitoring-report.clientreadygenerated", "CurrentDate": "2024-01-24T18:06:26.6788459Z", "Data": { "Timestamp": "2024-01-24T05:01:40.0903166Z", "Source": "Client UI", "UserId": "9345d242-4862-48b4-8a3d-b522dd3c903c", "UserFirstLastName": "David Smith", "WorkspaceId": 145481, "WorkspaceName": "MDC Advisors", "ClientBookId": 82286, "ClientBookName": "book", "ClientId": 431122, "ExternalClientId": "4324FDSFS4343", "ClientName": "ABC Manufacturing Corp.", "ClientEmails": "john@example.com, @susy@example.com", "PolicyName": "401k plan", "Accounts": [ { "AccountName": "Plan Example", "NumberOfParticipants": 15, "NumberOfFunds": 30, "TotalAccountBalance": 479823.91, "ProviderName": "ABC Recordkeeper", "ProviderNumber": "ABC123", "Investments": [ { "SecId": "F00000V1HO", "InvestmentName": "Vanguard Ultra-Short-Term Bond Admiral", "Ticker": "VUSFX", "PeerGroup": "Ultrashort Bond", "Fi360AvgScore1Year": 45, "Fi360AvgScore3Year": 20, "Fi360AvgScore5Year": 13, "Fi360AvgScore10Year": null, "Fi360Score": 36, "Action": "None", "Type": "Active", "Assets": 0 }, { "SecId": "F000010W2A", "InvestmentName": "SPDR® S&P Kensho New Economies Comps ETF", "Ticker": "KOMP", "PeerGroup": "Mid-Cap Growth", "Fi360AvgScore1Year": 54, "Fi360AvgScore3Year": null, "Fi360AvgScore5Year": null, "Fi360AvgScore10Year": null, "Fi360Score": 46, "Action": "Watch", "Type": "Passive", "Assets": 15022.38 }, { "SecId": "FOUSA07ZY8", "InvestmentName": "DFA Short-Term Extended Quality I", "Ticker": "DFEQX", "PeerGroup": "Short-Term Bond", "Fi360AvgScore1Year": 52, "Fi360AvgScore3Year": 65, "Fi360AvgScore5Year": 52, "Fi360AvgScore10Year": 34, "Fi360Score": 63, "Action": "Watch", "Type": "Active", "Assets": 14680.66 }, { "SecId": "F00000M8DA", "InvestmentName": "iShares MSCI EAFE Min Vol Factor ETF", "Ticker": "EFAV", "PeerGroup": "Foreign Large Blend", "Fi360AvgScore1Year": 84, "Fi360AvgScore3Year": 82, "Fi360AvgScore5Year": 57, "Fi360AvgScore10Year": null, "Fi360Score": 80, "Action": "Watch", "Type": "Passive", "Assets": 1510.78 }, { "SecId": "FOUSA00C28", "InvestmentName": "DFA Large Cap International I", "Ticker": "DFALX", "PeerGroup": "Foreign Large Blend", "Fi360AvgScore1Year": 0, "Fi360AvgScore3Year": 9, "Fi360AvgScore5Year": 11, "Fi360AvgScore10Year": 13, "Fi360Score": 0, "Action": "None", "Type": "Active", "Assets": 47718.1 }, { "SecId": "F00000V4K9", "InvestmentName": "iShares Exponential Technologies ETF", "Ticker": "XT", "PeerGroup": "Miscellaneous Sector", "Fi360AvgScore1Year": 8, "Fi360AvgScore3Year": 34, "Fi360AvgScore5Year": 26, "Fi360AvgScore10Year": null, "Fi360Score": 0, "Action": "None", "Type": "Passive", "Assets": 40470.58 }, { "SecId": "F00000ONW3", "InvestmentName": "Invesco S&P 500® High Div Low Vol ETF", "Ticker": "SPHD", "PeerGroup": "Large Value", "Fi360AvgScore1Year": 86, "Fi360AvgScore3Year": 83, "Fi360AvgScore5Year": 73, "Fi360AvgScore10Year": null, "Fi360Score": 85, "Action": "Watch", "Type": "Passive", "Assets": 636.3 } ] } ], "MonitoringPeriod": "Q4 2023", "ReportTemplateName": "~Good Example Report", "EndpointWorkspaceGuid": "ed87d688-edd3-4cf1-a823-c29706ffb214", "URL": "https://reportservicesapi.fi360.com/v1/webhookreports/download/5506c69e-b86a-4610-87fb-16e03e1bcca9?nonce=p6%252bU44ciYbhrPN8QNGhdmbj8tWTLcVWvtcIfHs74B0g%253d" } }
Event type
You will receive events for all of the event types your webhook endpoint is listening for in your configuration. Use the received EventType
to determine what processing your application needs to perform. The EventType
also determines the type of Fi360 object that’s included as the data
, as documented along with each event type in the API Reference.
The events currently available within the Fiduciary Focus Toolkit are below.
-
toolkit-monitoring-report.draft generated
- Occurs whenever a draft monitoring report is generated.
-
toolkit-monitoring-report.clientreadygenerated
- Occurs whenever a client ready monitoring report is generated.
-
toolkit-monitoring-report.completed
- Occurs whenever a monitoring report is marked as completed.
-
toolkit-monitoring-report.reproduce
- Occurs whenever a monitoring report is returned to a draft status.
-
toolkit-proposal-report.generated
- Occurs whenever a proposal report is generated.
-
toolkit-research-report.generated
- Occurs whenever a research report is generated.
-
Toolkit Data Update:New Quarterly Data Available
- Occurs whenever new investment data is posted for a calendar quarter-end. This signifies that a new quarterly monitoring record is now available for your clients.
API request events
When an event is generated, that request is indicated as the EventId
, which you can view in your Webhook Dashboard log viewer in Settings/Webhooks to see it's data
.
How to access the report PDF
For security reasons, theURL
needs combined with the EndpointWorkspaceGuid
to create the full path to the file as shown below. Also, once downloaded, the PDF cannot be re-downloaded via the same path again.
Full Path to PDF: URL
&ws=EndpointWorkspaceGuid
For Developers building your own Webhook endpoint (Sample C# Code)
Endpoint setup and event data capture
public class TestController : ControllerBase
{
//This key is displayed within the Fi360 Settings/Webhooks page once you register your endpoint URL.
//We recommend storing this key in a config file or database to accommodate changing it in the future
protected string signingKey = "s-d6ccae867d2844f4b01b3fb44b8bea584eb2ef9e11ca417da92b64c8b2932a";
[HttpPost]
[Route("webhook")]
public async Task<IActionResult> Post()
{
var currentRequest = HttpContext.Request;
var signature = currentRequest.Headers["WH-EVENT-SIGNATURE"];
var json = await new StreamReader(currentRequest.Body).ReadToEndAsync();
Console.WriteLine($"===={DateTime.Now} WEBHOOK EVENT RECEIVED====");
Console.WriteLine($"Signature: {signature}");
var mySignature = GetSignature(json);
Console.WriteLine($"Signatures Match: {signature.Equals(mySignature)}");
Console.WriteLine();
var = JsonConvert.DeserializeObject<EventInfo<Object>>(json);
if ( .EventType == "decision-analysis-report.locked")
{
var lockEvent = JsonConvert.DeserializeObject<EventInfo<ReportLockEvent>>(json);
var b64 = lockEvent.Data.PdfContent;
//do PDF stuff here
}
Console.WriteLine(json);
return Ok();
}
[HttpGet]
public IActionResult Get()
{
return Ok(new {Message="Test Receiver is Running" });
}
protected string GetSignature(string content)
{
byte[] key = Encoding.UTF8.GetBytes(signingKey);
using (HMACSHA512 hmac = new HMACSHA512(key))
{
var buffer = Encoding.UTF8.GetBytes(content);
var hashedValue = hmac.ComputeHash(buffer);
return Convert.ToBase64String(hashedValue);
}
}
}
To access the Analysis PDF, you can either convert the base64 data into a PDF via code or you can download the PDF from a short-lived URL that can be constructed using two fields from the event as noted above.
Convert the base64 encoded pdf data into an actual PDF file
byte[] pdf = Convert.FromBase64String(b64);
using(var file = new FileStream(@"C:\Temp\output.pdf", FileMode.OpenOrCreate, FileAccess.Write))
{
file.Write(pdf, 0, pdf.Length);
file.Flush();
}