Webhooks

Use Salsa's REST APIs to test and manage your Webhook endpoints.

Webhooks are used by Salsa to notify you of changes in your account. In this guide, you'll learn how to manage Webhook endpoints, verify messages, and ensure that your endpoints can receive events from our system.

Create a Webhook endpoint

To create a Webhook endpoint, send a POST request to webhook-endpoints. The endpointUrl is where Salsa will send events when changes are made in your account.

curl --request POST \
     --url https://api.sandbox.salsa.dev/api/rest/v1/webhook-endpoints \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'authorization: Bearer [your-auth-token]' \
     --data '
{
  "description": "This is my webhook endpoint that will receive events",
  "endpointUrl": "https://my-webhook-url.com/path",
}
'

Example response

{
  "data": {
    "id": "prtnrendpt_8ae05235-927e-449f-a40a-9611bc4794ee",
    "description": "This is my webhook endpoint that will receive events",
    "endpointUrl": "https://my-webhook-url.com/path",
    "status": "ENABLED",
    "webhookVersion": "2023-01-01",
    "signatureSecret": "some-secret"
  }
}

By default, when a Webhook is created, status is set as Enabled. This means that events will be sent to this Webhook endpoint. For more information, see Create WebhookEndpoint.

Update a Webhook endpoint

If you need to update an endpointURL, description, or status, send a PUT request to webhook-endpoints. If you disable the endpoint, Salsa will no longer send Webhook events to the endpoint.

curl --request PUT \
     --url https://api.sandbox.salsa.dev/api/rest/v1/webhook-endpoints \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'authorization: Bearer [your-auth-token]' \
     --data '
{
  "description": "This is my updated webhook endpoint that will receive events",
  "endpointUrl": "https://my-webhook-url.com/new/path",
  "status": "ENABLED",
  "webhookVersion": "2022-08-22"
}
'

Example response

{
  "data": {
    "id": "prtnrendpt_8ae05235-927e-449f-a40a-9611bc4794ee",
    "description": "This is my updated webhook endpoint that will receive events",
    "endpointUrl": "https://my-webhook-url.com/new/pathpath",
    "status": "ENABLED",
    "webhookVersion": "2023-01-01"
  }
}

Secure your Webhoook

Each Webhook call includes three headers that include information used for verification:

  • Webhook-Id: A unique identifier for the message. Note: If a Webhook event is resent due to failure, the same ID is used.
  • Webhook-Timestamp: A timestamp in seconds since epoch.
  • Webhook-Signature: A space delimited Base64 encoded list of signatures.

Sign content

To sign content, concatenate the Webhook ID, timestamp, and payload body using .. The signature is sensitive to any changes. This means that you should not change the body in any way before verifying.

Here's an example:

signed_content = "${webhook_id}.${webhook_timestamp}.${body}"

Calculate the expected signature

Salsa uses HMAC with SHA-256 to sign its Webhooks. To create a signature, you'll need the secret key returned as signatureSecret in the Create Webhook Endpoint response body.

📘

Secret key rotation

Contact support if you need to rotate the secret.

To calculate the expected signature, HMAC the signed_content from the base64 portion of your signing secret (this is everything after whsec_). For example, from the secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw, you'll only need MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw. The signature generated should match one of the signatures provided by the Webhook-Signature header.

The Webhook-Signature header is composed of a list of space delimited signatures and their corresponding version identifiers. For example:

v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=

Make sure to remove the version prefix and delimiter (for example, v1,) before verifying the signature.

🚧

Prevent attacks

Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.

This is an example written in JavaScript, that uses actual header and body values.

const webhook_id = "msg_p5jXN8AQM9LWM0D4loKWxJek"
const webhook_timestamp = "1614265330"
const webhook_signature = "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="
const body = `{\"test\": 2432232314}`
console.log("Signature: ", webhook_signature)

const endpoint_secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
const base64_secret = "MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
const secret =  Buffer.from(base64_secret, 'base64');

const signed_content = `${webhook_id}.${webhook_timestamp}.${body}`
let hmac = crypto.createHmac('sha256', secret).update(signed_content).digest('base64');

console.log("Verifying with: ", hmac);
<?php

$webhook_id = "msg_p5jXN8AQM9LWM0D4loKWxJek";
$webhook_timestamp = "1614265330";
$webhook_signature = "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=";
$body = '{"test": 2432232314}';
echo "Signature: $webhook_signature\n";

$endpoint_secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
$base64_secret = "MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
$secret = base64_decode($base64_secret);

$signed_content = "$webhook_id.$webhook_timestamp.$body";
$hmac = hash_hmac('sha256', $signed_content, $secret, true);
$hmac_base64 = base64_encode($hmac);

echo "Verifying with: $hmac_base64\n";
?>

Verify the timestamp

Salsa also sends the timestamp of the attempt in the Webhook-Timestamp header. Compare this timestamp against your system timestamp to ensure it's within your tolerance to prevent attacks. We recommend a window of 10 minutes.

Test an endpoint

The easiest way to test your Webhook endpoint, and ensure it's configured correctly is to trigger a few events, such as:

  • Creating or updating an Employer
  • Creating or updating a Worker

You'll receive a Webhook event for every Employer/Worker created or edited.

Sample Webhook event

{ 
  "data": 
  { 
    "type": "Event.name",
    "attributes": 
    { 
      ... # event specific schema
    }
  } 
}
{ 
  "data": 
  { 
    "type": "Employer.created",
    "attributes": 
    { 
      "employerId": "er_123",
      "externalId": "abc",
    }
  } 
}

Webhook events

The following actions trigger Webhooks events:

  • When an Employer is created or updated
  • When a Worker is created or updated
  • When onboarding status for an Employer or Worker changes
  • When an Employer is reminded to run payroll
  • When Payroll Run is awaiting input
  • When Payroll Run is confirmed
  • When a worker is paid

Webhooks behave the same in the Sandbox environment as they do in production.

🚧

Notification.workerPayday

Notification.workerPayday is not be sent in the Sandbox environment for workers who are paid via Direct Deposit. This event can be tested in the Sandbox environment by choosing to pay a worker with the Paper Check method.

Retry Schedule

Salsa attempts to deliver each Webhook message based on a retry schedule with exponential backoff. This schedule is used following a failure:

  • Immediately
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
  • 10 hours (in addition to the previous)

For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.

If an endpoint is disabled, delivery attempts to the endpoint will be disabled.