Common use cases
- Send internal notifications when documents are signed
- Update your database when signing requests are completed
- Trigger downstream workflows (invoicing, provisioning, etc.)
- Track signing request status changes in real-time
Event types
Firma sends the following event types:Signing Request Events
signing_request.created- New signing request createdsigning_request.sent- Signing request sent to recipientssigning_request.viewed- Recipient viewed the documentsigning_request.completed- All recipients finished signingsigning_request.expired- Signing request expiredsigning_request.cancelled- Signing request was cancelledsigning_request.updated- Signing request metadata updatedsigning_request.deleted- Signing request deleted (before sending)signing_request.certificate.generated- Signing certificate generatedsigning_request.reminder.sent- Reminder sent to recipients
Recipient Events
signing_request.recipient.signed- Recipient completed signingsigning_request.recipient.declined- Recipient declined to signsigning_request.recipient.identity_changed- Recipient changed their identity (name, company, etc.) during signing
Template Events
template.updated- Template modifiedtemplate.used- Template used to create a signing request
Organization Seal Events
organization_seal.created- New seal createdorganization_seal.updated- Seal image replaced (new version), renamed, or default flag changedorganization_seal.deleted- Seal revokedorganization_seal.erased- Revoked seal lineage permanently erased
Signing Request Seal Events
signing_request.seal.applied- Seal applied to a documentsigning_request.seal.paused- Seal participant paused (revoked seal with stop pending, or integrity mismatch)signing_request.seal.swapped- Paused seal participant swapped to a different seal
Workspace Events
workspace.created- New workspace createdworkspace.updated- Workspace modified
Domain Events
domain.verified- Domain verified successfullydomain.verification.failed- Domain verification failed
Two levels of enablement
Webhook delivery requires the webhook’s own switch and the master switch for its scope. Company webhooks use the company master switch. Workspace webhooks use that workspace’s master switch and do not require the company switch.
At least one scope (company or workspace) must have its master switch on for any webhook under that scope to deliver. Turning on the workspace master switch also generates a signing secret for that workspace if one doesn’t already exist.
When the master switch is off, Firma skips the event before logging a delivery attempt.
consecutive_failures stays at 0 and the webhook shows no failure history, even though nothing is being delivered - there’s no error to alert you.
Permissions
Toggling the company-level master switch requires owner or admin access to the company; view-only members cannot enable or disable it. Attempting to toggle it without sufficient permissions returns a permission error.Creating a webhook
Create webhooks via the API or dashboard:Your webhook URL must use HTTPS and respond within 5 seconds. Use
POST /webhooks/{id}/test after creation to verify your endpoint is receiving events correctly.Webhook payload structure
All webhook events follow this standard structure:Test deliveries from
POST /webhooks/{id}/test use a different shape: type (webhook.test), webhook_id, timestamp and data. They have no id, so skip your idempotency check for them.Security: Signature verification (Required)
Firma signs all webhook requests using HMAC SHA-256. Your webhook endpoint receives these headers:X-Firma-Signature- HMAC signature using current signing secretX-Firma-Signature-Old- HMAC signature using previous secret (during 7-day rotation grace period)X-Firma-Event- Event type (e.g.,signing_request.completed)X-Firma-Delivery- ID of this event’s delivery to your endpoint. It stays the same on every retry, automatic or manual. Use the payloadidto identify the event.
Signature format
Firma uses a timestamped signature header:- Header:
X-Firma-Signature: t=1707500000,v1=abc123def456... t= Unix timestamp (seconds) when the signature was generatedv1= HMAC-SHA256 hex digest- Signed payload format:
{timestamp}.{json_body} - The HMAC key is your signing secret exactly as shown in the dashboard (64 characters). Don’t hex-decode it.
X-Firma-SignatureandX-Firma-Signature-Oldeach carry their ownt, so read the timestamp from the header you are verifying.
Get your signing secret
- Navigate to your Firma dashboard
- View webhook details to retrieve the signing secret
- Store the secret securely (environment variable or secrets manager)
Verification example - Node.js (Express)
Verification example - Python (Flask)
Handling secret rotation
Rotate your webhook signing secret from the dashboard, or withPOST /webhooks/rotate-secret for company webhooks or POST /workspaces/{id}/webhooks/rotate-secret for workspace webhooks. Through the API, you can rotate a secret once per hour. Workspace webhooks are signed with that workspace’s own secret, not the company secret.
- Firma generates a new secret
- For 7 days, Firma sends both signatures:
X-Firma-Signature(new secret)X-Firma-Signature-Old(previous secret)
- After 7 days, only
X-Firma-Signatureis sent.GET /webhooks/secret-statusorGET /workspaces/{id}/webhooks/secret-statusreturnsgrace_period_ends_at.
X-Firma-Signature with the secret your endpoint is configured with. If that fails, verify X-Firma-Signature-Old with the same secret. Until you deploy the new secret, your configured secret is the previous one, which is the one X-Firma-Signature-Old is signed with. After you deploy it, X-Firma-Signature matches. Deploy the new secret within the 7 days, and configure one secret at a time: an endpoint that accepts both secrets keeps trusting the old one.
If a secret may be compromised
- Rotate the secret immediately.
- Deploy the new secret to your endpoint straight away.
X-Firma-Signature-Old, so the 7-day overlap does not extend trust in the old secret.
Retry behavior
Firma does not follow HTTP redirects when delivering webhooks. Register the exact URL your server serves, including its scheme, hostname, path, and trailing-slash form. Firma automatically retries failed webhook deliveries:- Attempts: Up to 3 per event: immediately, then at least 5 minutes after the first failure, then at least 1 hour after the second. Retries run on a 5-minute cycle, so expect roughly 5 to 10 minutes and 60 to 65 minutes, or later when many retries are queued.
- Timeout: Your endpoint must respond within 5 seconds
- Success: Any 2xx status code indicates success. Redirects count as failures.
- Same event, new signature: Every attempt carries the same
X-Firma-Deliveryand, for real events, the same payloadid, and is signed again with a fresh timestamp. The JSON key order of a retried body can differ from the first attempt, so always verify the raw body exactly as received. - Auto-disable: When
consecutive_failuresreaches 50, the webhook is disabled automatically and Firma emails the account owner. Re-enable it withPUT /webhooks/{id}("enabled": true) or in the dashboard. - After the last attempt: An event that fails all 3 attempts is not retried automatically. An administrator can retry it from the webhook event log in the dashboard.
- Not retried: Test deliveries from
POST /webhooks/{id}/testare sent once. A delivery to a URL that fails Firma’s delivery-time address check (for example, a URL that resolves to a private or loopback address) fails without a retry.
Delivery limits
Webhooks are notifications, not a complete event log:- Events are not delivered in a guaranteed order, and a retried event can arrive after a later one. Use the payload
created_atas a hint and fetch the current state from the API. - Events raised while a webhook or its master switch is off are not stored and are not delivered later. Re-enabling a webhook does not replay them.
- When a webhook already has 100 events from the last 60 seconds, new events for it are skipped and never delivered.
- There is no API to list or replay events. To reconcile after an outage, list signing requests with
GET /signing-requests(for examplestatus=finished&sort_by=finished_on&sort_order=desc) and fetch each one withGET /signing-requests/{id}.
Idempotency
Always handle duplicate events using the eventid:
Monitoring webhook health
Monitor your webhook’s health by checking the webhook details:consecutive_failures- Failure counter used for automatic disabling. It resets to0after a successful delivery.enabled- Whether the webhook is active. It is disabled automatically whenconsecutive_failuresreaches 50.auto_disabled_at- When the webhook was disabled automatically, otherwisenull
Rate limit: Each
/webhooks endpoint allows 60 requests per minute per API key. POST /webhooks/{id}/test allows 10 requests per minute.Troubleshooting
Common issues
Webhooks configured but events don’t fire This is the most common cause of “my webhook isn’t working” reports. Per-webhook settings can be entirely correct while the account-level master switch is off, silently skipping every event.- Confirm the master switch is on for the right scope: company-level webhooks need the company’s switch on, workspace-level webhooks need that workspace’s switch on.
- Don’t trust a passing test as proof -
POST /webhooks/{id}/testbypasses the master switch, so it can succeed while live events don’t fire. - Check
consecutive_failureson the webhook - if it reads0and no events show up, that also points to the master switch, since skipped events are never logged as failed delivery attempts. - If you can’t find or toggle the master switch, you may not have owner or admin permissions on the company; ask an owner or admin to enable it from Settings > Webhooks.
- Verify you’re using the correct signing secret
- Check that you’re hashing the raw request body (not parsed JSON)
- Ensure you’re using HMAC SHA-256, not other hash algorithms
- Respond with 200 immediately, process asynchronously
- Check your endpoint responds within 5 seconds
- Use background jobs/queues for heavy processing
- Implement idempotency using the event
id - Store processed event IDs in your database
- Check
consecutive_failuresand recent events in the webhook event log in the dashboard - Fix endpoint issues, then re-enable webhook via API or dashboard
Testing webhooks locally
Use a tunnel service like ngrok for local development:Production checklist
- Verify HMAC signatures on all webhook requests
- Handle signature rotation (check both
X-Firma-SignatureandX-Firma-Signature-Old) - Respond with 200 within 5 seconds
- Process events asynchronously (queues/background jobs)
- Implement idempotency using the event
id - Store signing secret securely (env var or secrets manager)
- Monitor
consecutive_failuresmetric via GET webhook endpoint - Set up alerts for webhook failures
- Log all webhook events for debugging
- Test with all subscribed event types
- Stay within rate limits (See Rate Limit Guide)
Next steps
- Create a webhook via API
- Test your webhook before going live
- Update webhook settings as needed