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 theresource.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 identifiertype: Event type (e.g.,customer.created)data: Object containing the entityid
| Resource | Events |
|---|---|
| Business | business.created, business.updated, business.archived |
| Customer | customer.created, customer.updated, customer.archived |
| Vendor | vendor.created, vendor.updated, vendor.archived |
| Invoice | invoice.created, invoice.updated, invoice.archived |
| Invoice Payment | payment.created, payment.updated, payment.archived |
| Journal Entry | journal_entry.created, journal_entry.updated, journal_entry.archived |
| Bank Transaction | bank_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 theactivity.* 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 identifiertype: Event type withactivity.prefix (e.g.,activity.categorize_bank_transaction)data: Object containing relevant identifiers for the activity
activity.categorize_bank_transaction
activity.load_pnlactivity.load_balance_sheetactivity.load_cashflow_statement
activity.create_custom_accountactivity.archive_custom_accountactivity.reactivate_custom_accountactivity.upload_custom_account_csv
activity.export_pnl_csvactivity.export_pnl_excelactivity.export_pnl_comparison_csvactivity.export_pnl_month_over_monthactivity.export_pnl_line_item_excelactivity.export_balance_sheet_csvactivity.export_balance_sheet_excelactivity.export_cashflow_csvactivity.export_transactions_csvactivity.export_transactions_excelactivity.export_pnl_pdfactivity.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.createdoractivity.export_pnl_csv
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: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:| Attempt | Delay After Previous Failure |
|---|---|
| 1 | Immediately |
| 2 | 5 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 5 hours |
| 7 | 10 hours |
| 8 | 10 hours |
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., usingJSON.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:- Navigate to the message in the Layer developer portal
- Click the options menu next to any of the delivery attempts
- Select “Resend” to send the message to your endpoint again
- Navigate to the endpoint’s details page
- Click “Options > Recover Failed Messages”
- Select the time window from which to recover messages
- Navigate to any message on the endpoint page
- Click the options menu for that message
- Select “Replay…” and choose “Replay all failed messages since this time”