Skip to main content

Introduction

Webhooks are how Layer notifies your application of events in real-time. At their core, they are HTTP POST requests sent to a pre-determined endpoint that you control. You can configure webhook endpoints from the Layer developer portal. Your endpoint will listen to the event types you specify, allowing you to react to changes in your customers’ data as they happen. Requirements for webhook endpoints:
  • Response Status: Your endpoint must return an HTTP status code in the 2xx range (200-299) to indicate successful receipt. Any other status code will be treated as a failure and trigger the retry mechanism.
  • Response Time: Your endpoint must respond within 15 seconds. Requests that exceed this timeout will be considered failed and will be retried.
  • CSRF Protection: Disable CSRF (Cross-Site Request Forgery) protection for your webhook endpoint if your framework enables it by default, as webhook requests will not include CSRF tokens.
  • Signature Verification: Always verify the webhook signature to ensure the request is authentic. See the signature verification section for implementation details.

Event Types

Layer supports two categories of webhook events, each with distinct naming conventions to make filtering and subscription management easier.

Entity Lifecycle Events

Entity lifecycle events follow the resource.event naming pattern (e.g., customer.created, invoice.updated). These events are triggered when data entities are created, updated, or archived in Layer. Layer uses thin event types, meaning the webhook payload includes the entity id but not the full object. You’ll need to fetch the complete object using the appropriate API endpoint. Event structure:
  • id: Unique event instance identifier
  • type: Event type (e.g., customer.created)
  • data: Object containing the entity id
Example payload:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "customer.created",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
All core financial event domain objects are available for lifecycle data objects as well as all ledger entry lifecycle events. Commonly used data types are below:
ResourceEvents
Businessbusiness.created, business.updated, business.archived
Customercustomer.created, customer.updated, customer.archived
Vendorvendor.created, vendor.updated, vendor.archived
Invoiceinvoice.created, invoice.updated, invoice.archived
Invoice Paymentpayment.created, payment.updated, payment.archived
Journal Entryjournal_entry.created, journal_entry.updated, journal_entry.archived
Bank Transactionbank_transaction.created, bank_transaction.updated, bank_transaction.archived

Customer Activity Events

Customer activity events track user actions within the Layer platform. These events help you monitor how your customers are using Layer’s features. Activity events use the activity.* namespace to distinguish them from entity lifecycle events. This makes it easy to subscribe to all activity events or filter them as a group. Event structure:
  • id: Unique event instance identifier
  • type: Event type with activity. prefix (e.g., activity.categorize_bank_transaction)
  • data: Object containing relevant identifiers for the activity
Example payload:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "activity.categorize_bank_transaction",
  "data": {
    "transaction_id": "txn_9876543210fedcba"
  }
}
Available activity event types: Transaction Management:
  • activity.categorize_bank_transaction
Report Generation:
  • activity.load_pnl
  • activity.load_balance_sheet
  • activity.load_cashflow_statement
Account Management:
  • activity.create_custom_account
  • activity.archive_custom_account
  • activity.reactivate_custom_account
  • activity.upload_custom_account_csv
Export Activities:
  • activity.export_pnl_csv
  • activity.export_pnl_excel
  • activity.export_pnl_comparison_csv
  • activity.export_pnl_month_over_month
  • activity.export_pnl_line_item_excel
  • activity.export_balance_sheet_csv
  • activity.export_balance_sheet_excel
  • activity.export_cashflow_csv
  • activity.export_transactions_csv
  • activity.export_transactions_excel
  • activity.export_pnl_pdf
  • activity.generate_tax_packet

Subscribing to Event Types

When configuring your webhook endpoint, you can:
  • Subscribe to all events: Leave event type filters empty to receive both lifecycle and activity events
  • Subscribe to all lifecycle events: Use a pattern filter for specific resources (e.g., customer.* for all customer events)
  • Subscribe to all activity events: Filter for activity.* to receive only user activity events
  • Subscribe to specific events: Select individual event types like invoice.created or activity.export_pnl_csv
The period-delimited naming convention allows the Layer developer portal to logically group events, making it easier to find and subscribe to the events you need.

Adding an Endpoint

To start receiving webhooks, you need to configure your endpoints in the Layer developer portal. Adding an endpoint is as simple as providing a URL that you control and selecting the event types you want to listen to. If you don’t specify any event types, by default, your endpoint will receive all events, regardless of type. This can be helpful for getting started and for testing, but we recommend changing this to a subset later on to avoid receiving extraneous messages.

Testing Endpoints

Once you’ve added an endpoint, you’ll want to make sure it’s working correctly. The “Testing” tab in the Layer developer portal lets you send test events to your endpoint. After sending an example event, you can click into the message to view the message payload, all of the message attempts, and whether it succeeded or failed.

Verifying Signatures

Webhook signatures let you verify that webhook messages are actually sent by Layer and not a malicious actor. You should always verify webhook signatures in production. Layer’s webhook partner Svix offers a set of useful libraries that make verifying webhooks very simple. Here is an example using JavaScript:
import { Webhook } from "svix";

const secret = "YOUR_WEBHOOK_SECRET"; // Get this from the Layer developer portal

// These were all sent from the server
const headers = {
  "svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "svix-timestamp": "1614265330",
  "svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';

const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const verifiedPayload = wh.verify(payload, headers);
For more instructions and examples in other languages (Python, Go, Ruby, Java, Rust, C#, PHP, Kotlin, Swift), check out the Svix webhook verification documentation.

Retry Schedule

Layer attempts to deliver each webhook message based on a retry schedule with exponential backoff.

The Schedule

Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:
AttemptDelay After Previous Failure
1Immediately
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours
If an endpoint is removed or disabled, delivery attempts to the endpoint will be disabled as well. For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.

Manual Retries

You can use the Layer developer portal to manually retry each message at any time, or automatically retry (“Recover”) all failed messages starting from a given date.

Troubleshooting

There are some common reasons why your webhook endpoint might be failing:

Not Using the Raw Payload Body

This is the most common issue. When generating the signed content, we use the raw string body of the message payload. If you parse the JSON and then re-serialize it (e.g., using JSON.stringify() in JavaScript), different implementations may produce different string representations of the JSON object, which can lead to discrepancies when verifying the signature. You must verify the signature using the exact raw request body bytes as received from Layer, not a re-serialized version.

Missing the Secret Key

From time to time we see people simply using the wrong secret key. Remember that keys are unique to endpoints.

Sending the Wrong Response Codes

When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when messages are supposed to succeed vs fail.

Responses Timing Out

We will consider any message that fails to send a response within 15 seconds a failed message. If your endpoint is also processing complicated workflows, it may timeout and result in failed messages. We suggest having your endpoint simply receive the message and add it to a queue to be processed asynchronously so you can respond promptly and avoid getting timed out.

Failure Recovery

Re-enable a Disabled Endpoint

If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. To re-enable a disabled endpoint, go to the Layer developer portal, find the endpoint from the list and select “Enable Endpoint”.

Recovering/Resending Failed Messages

If your service experiences downtime or your endpoint was misconfigured, you can recover messages that failed during that period. The Layer developer portal provides several recovery options: Single Message Replay To resend an individual message:
  1. Navigate to the message in the Layer developer portal
  2. Click the options menu next to any of the delivery attempts
  3. Select “Resend” to send the message to your endpoint again
Bulk Recovery from Service Outage To replay all failed messages since a specific time:
  1. Navigate to the endpoint’s details page
  2. Click “Options > Recover Failed Messages”
  3. Select the time window from which to recover messages
Time-Based Granular Recovery To recover all failed messages from a specific timestamp:
  1. Navigate to any message on the endpoint page
  2. Click the options menu for that message
  3. Select “Replay…” and choose “Replay all failed messages since this time”