Skip to main content
Version: v2

Webhooks

Sync Expectations

The following table describes the sync behavior of Fullstory events to webhooks.

DestinationSync IntervalUpdate WindowRelevant TimestampsSetup Guide
WebhooksWhen an event occurs which matches a stream definitionEvents are streamed as they occur and are not subsequently updatedtimestampWebhooks Help Doc

There will be some observed latency between an event occurring and Fullstory sending an HTTP request to your webhook endpoint. This latency will vary based on processing time and other factors including when each event is reported to Fullstory. For example, in disconnected client scenarios Fullstory may be notified about an event minutes or even hours after the event occurred.

Payload

The following table contains the request body schema for Fullstory events sent to webhook destinations.

FieldTypeDescription
nameSTRINGThe event name as defined on the webhook stream.
timestampTIMESTAMPThe time in UTC when the final event occurred matching the webhook stream definition.
api_versionSTRINGThe version string which corresponds to the overall payload schema. Currently "v2".
signal_versionSTRINGThe version of the webhook stream definition which was matched. Increases when the stream definition is modified.
userUSERDetails about the user associated with the matched session. See the USER type schema.

User

The following table contains the schema for the USER type.

FieldTypeDescription
idSTRINGThe Fullstory assigned user ID.
uidSTRINGThe application-specific ID you've given to the user.
emailSTRINGThe email address associated with the user.
display_nameSTRINGThe name associated with the user.

Example

The following is an example webhook destination request body.

{
"name": "Test Event",
"timestamp": "2024-01-02T03:04:05Z",
"api_version": "v2",
"signal_version": "1",
"user": {
"id": "2",
"uid": "3",
"email" : "daniel.falko@example.com",
"display_name": "Daniel Falko"
}
}

Delivery Guarantees

Fullstory attempts to send each matching event to a webhook stream only once. However, there are rare cases where an event may be sent multiple times to a stream.

If the response from your receiving webhook endpoint indicates the event wasn't successfully processed, Fullstory will retry sending an event up to 30 times over a 5 hour period following the initial attempt.

Fullstory will retry sending an event if any of the following are true:

  • The request to the receiving endpoint times out (Fullstory does not receive a response from your webhook endpoint within 10 seconds)
  • There's a general network error communicating with the receiving endpoint (e.g. connection closed)
  • The response HTTP status code is between 500 and 599
  • The response HTTP status code is 302, 303, 307, or 429

Fullstory will not retry sending an event if any of the following are true:

  • The receiving endpoint IP address is not resolved to a public IP address
  • There's an issue with the overall stream configuration (e.g. an invalid URL is specified)
  • The response HTTP status code is any code not specified in retryable criteria above

Rate Limits

Fullstory does not rate limit outbound webhook streams. Since webhook stream events are matched at most once per session, your receiving endpoint should be prepared to handle a ceiling of your total session capture rate. If your Fullstory account captures 50 sessions per second, your receiving endpoint could receive 50 requests per second if every session matched your webhook stream definition.

IP Addresses

Fullstory will send webhook requests from the following IP addresses.

US Region

8.35.195.0/29

EU Region

34.89.210.80/29

Authentication with the Shared Secret

If you provide a secret value in your webhook stream configuration, Fullstory will include a signature in the request headers sent to your webhook endpoint. We recommend verifying this signature to ensure all requests are sent by Fullstory.

To authenticate the incoming request, your endpoint implementation needs to compute a request signature and then compare that signature to the one actually included in the request. If the two signatures match, the request is valid. Otherwise, the request is invalid and should be rejected with a "401 Unauthenticated" HTTP status code. If the timestamp of when the request was created is too old, the request could be a replay attack, in which case the request should be rejected (even if the signature is correct).

Computing a Request Signature

In the steps listed above, the first step was to compute a request signature using the shared secret. This operation requires four different inputs:

  1. The ID of your organization. This is an identifier provided by Fullstory that uniquely identifies your account. If you use the Umbrella Management feature of Fullstory, you may have multiple accounts, each with its own ID. If you were to configure multiple accounts to send webhooks to the same endpoint, you would then need to use the ID provided in the request to know from which account the event originated.
  2. The timestamp at which the request was made. This is not a timestamp you can attain by looking at the current time on a clock. This, too, is provided in the request.
  3. The event payload. This is the body of the POST request your endpoint receives.
  4. The shared secret. This is not provided in the request. The endpoint implementation code must have access to the very same secret that was provided when the endpoint was configured in Fullstory settings.

The first three are all provided in the request. The event payload is the request body for the HTTP request. But the other two values must be parsed out of the Fullstory-Signature request header. This header is a string that is a comma-delimited list of key-value pairs. Each pair is in the format key:value, and there will be three such pairs:

  1. o: The "o" stands for "organization". The value paired with this key is the ID for your account.
  2. t: The "t" stands for "timestamp". The value paired with this key is an integer value that represents a unix timestamp: seconds since the epoch (midnight on January 1, 1970 UTC).
  3. v: The "v" stands for "value". The header is named "Fullstory-Signature", so the value paired with this key is the actual signature value. It is a base64-encoded string of bytes.

The signature is computed by computing an HMAC keyed hash using the SHA256 algorithm. The key is the shared secret. The data being hashed is the canonical event payload. This canonical payload has the following format: {payload}:{ID}:{timestamp}. In this format, {payload} is the actual HTTP request body — the event payload; {ID} is the ID of your organization (provided in the signature header); and {timestamp} is the integer timestamp when the request was created (also provided in the signature header).

To compute a signature, the endpoint implementation code must parse the signature header as described above and then construct the canonical event payload by combining the request body as well as the "o" and "t" values that were encoded in the signature header. Below is an example:

TypeValue
HTTP request body{ "name": "Test Event", "timestamp": "2024-01-02T03:04:05Z", "api_version": "v2", "signal_version": "1", "user": { "id": "2", "uid": "3", "email" : "daniel.falko@example.com", "display_name": "Daniel Falko" } }
Fullstory-Signature headero:TN1,t:1578598083,v:pZKkkdmsGimaA30SsVHA9U93TS/G0skNAE16XyoQhAQ=
Shared secreta1618333f9471311g173033fcd370b8
Canonical Event Payload{ "name": "Test Event", "timestamp": "2024-01-02T03:04:05Z", "api_version": "v2", "signal_version": "1", "user": { "id": "2", "uid": "3", "email" : "daniel.falko@example.com", "display_name": "Daniel Falko" } }:TN1:1578598083

After using the shared secret and canonical event payload to compute the HMAC keyed hash, the result is the signature.

Validating the Request Signature

The final step is to validate the request’s signature. This is done by base64-decoding the "v" value from the signature header and comparing it to the signature you just computed. If the two do not match (e.g. they don’t represent the exact same sequence of bytes), then the request’s signature is invalid, and the request should be rejected.

Checking the Request Timestamp

Even if the signature is valid, the endpoint implementation should also take the given timestamp, the "t" value from the signature header, and compare it to the current timestamp. Such a check guards against replay attacks, where old messages could be re-delivered in whole later. Replayed messages will have a valid signature, since the request body would be identical to an earlier request (unless the shared secret has since changed). So the most efficient way to discover they are not valid and don’t need to be processed is to check the timestamp.

It is likely that the two timestamps will not match since Fullstory’s servers and your servers will not have perfectly synchronized clocks, and there is some delay due to network transit and processing time. Because of the clock skew between servers, it is even possible for the time indicated in the request to be in the future.

A reasonable rule of thumb is to reject the request if the difference between your current time and the given timestamp is greater than five minutes. For this reason, it is also good practice to use NTP to keep your servers’ clocks synchronized, to help ensure that your clocks are within five minutes of ours.