> For the complete documentation index, see [llms.txt](https://docs.adaptria.locaria.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.adaptria.locaria.com/client-api/reference/webhooks.md).

# 🔔 Webhooks

### Webhooks

Receive **HTTPS POST callbacks** when RFQs, quotes, jobs, or projects change for a company you select at registration.

#### Supported Events

**RFQ**: `rfq.status_changed`, `rfq.submitted`, `rfq.info_submitted`, `rfq.assets_uploaded`, `rfq.copied`, `rfq.cancelled`

**Quote**: `quote.created`, `quote.status_changed`, `quote.sent_to_client`, `quote.accepted`, `quote.rejected`, `quote.revised_and_sent`, `quote.cancelled`, `quote.converted_to_project`

**Job**: `job.created`, `job.started`, `job.talent_assigned`, `job.request_rejected`, `job.reassigned`, `job.delivery_submitted`, `job.delivery_updated`, `job.delivery_approved`, `job.delivery_rejected`, `job.approved_for_invoicing`, `job.cancelled` (`job.feedback_submitted`, `job.archived`: reserved, not yet emitted)

**Project**: `project.status_changed`, `project.created`, `project.team_updated`, `project.sent_for_billing`, `project.delivered`, `project.copied`, `project.cancelled`

#### How It Works

1. **Register** a webhook URL + company scope → receive a signing secret (shown once)
2. When events occur, we POST to your URL with `X-Webhook-Signature` (HMAC-SHA256) and `X-Webhook-Event` headers
3. One active registration per API user — re-registering overwrites the previous webhook

#### Verifying Signatures

```javascript
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const expected = Buffer.from(signature, 'utf8');
const received = Buffer.from(req.headers['x-webhook-signature'], 'utf8');
const isValid = expected.length === received.length && crypto.timingSafeEqual(expected, received);
```

## Get current webhook registration

> Returns the current webhook registration for the authenticated API user. The signing secret is \*\*not\*\* included in this response.

````json
{"openapi":"3.1.0","info":{"title":"Adaptria Public API: Client","version":"1.0.0"},"tags":[{"name":"🔔 Webhooks","description":"## Webhooks\n\nReceive **HTTPS POST callbacks** when RFQs, quotes, jobs, or projects change for a company you select at registration.\n\n### Supported Events\n**RFQ**: `rfq.status_changed`, `rfq.submitted`, `rfq.info_submitted`, `rfq.assets_uploaded`, `rfq.copied`, `rfq.cancelled`\n\n**Quote**: `quote.created`, `quote.status_changed`, `quote.sent_to_client`, `quote.accepted`, `quote.rejected`, `quote.revised_and_sent`, `quote.cancelled`, `quote.converted_to_project`\n\n**Job**: `job.created`, `job.started`, `job.talent_assigned`, `job.request_rejected`, `job.reassigned`, `job.delivery_submitted`, `job.delivery_updated`, `job.delivery_approved`, `job.delivery_rejected`, `job.approved_for_invoicing`, `job.cancelled` (`job.feedback_submitted`, `job.archived`: reserved, not yet emitted)\n\n**Project**: `project.status_changed`, `project.created`, `project.team_updated`, `project.sent_for_billing`, `project.delivered`, `project.copied`, `project.cancelled`\n\n### How It Works\n1. **Register** a webhook URL + company scope → receive a signing secret (shown once)\n2. When events occur, we POST to your URL with `X-Webhook-Signature` (HMAC-SHA256) and `X-Webhook-Event` headers\n3. One active registration per API user — re-registering overwrites the previous webhook\n\n### Verifying Signatures\n```javascript\nconst crypto = require('crypto');\nconst signature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');\nconst expected = Buffer.from(signature, 'utf8');\nconst received = Buffer.from(req.headers['x-webhook-signature'], 'utf8');\nconst isValid = expected.length === received.length && crypto.timingSafeEqual(expected, received);\n```\n"}],"servers":[{"url":"https://api.adaptria.locaria.com","description":"Production Environment"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"Api-key","description":"## API Key Authentication\n\nStatic API key obtained from the Adaptria Portal or via `POST /api/credentials/api-keys`.\n\n**How to authenticate**:\n1. Generate an API key from the Adaptria Portal (Settings → API Keys) or ask your admin\n2. Include the key in all requests using the `Api-key` header\n\n**Key format**: `lla_live_<32-character-string>`\n\n**No token exchange needed** — the key is used directly in every request. Revoke or rotate keys from the portal."}},"schemas":{"WebhookGetResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Webhook registration ID"},"url":{"type":"string","format":"uri","description":"Registered callback URL"},"isActive":{"type":"boolean","description":"Whether the webhook is active"},"createdAt":{"type":"string","description":"Registration timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"}},"required":["id","url","isActive","createdAt","updatedAt"]}}},"paths":{"/api/public/v1/webhooks":{"get":{"summary":"Get current webhook registration","description":"Returns the current webhook registration for the authenticated API user. The signing secret is **not** included in this response.","tags":["🔔 Webhooks"],"responses":{"200":{"description":"Current webhook registration (no secret)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/WebhookGetResponse"}},"required":["success","data"]}}}},"404":{"description":"No webhook registration found"}}}}}}
````

## Register or replace webhook

> Register a webhook URL to receive HTTPS POST callbacks when events occur in any company the authenticated user belongs to.\
> \
> \*\*One active registration per API user\*\* — re-registering overwrites the previous webhook.\
> \
> The signing \`secret\` is returned \*\*only once\*\* at registration time. Store it securely to verify inbound callbacks.\
> \
> \### Supported Events\
> \*\*RFQ\*\*\
> \| Event | Trigger |\
> \|-------|---------|\
> \| \`rfq.status\_changed\` | RFQ status changes (any transition) |\
> \| \`rfq.submitted\` | RFQ submitted (DRAFT → PENDING) |\
> \| \`rfq.info\_submitted\` | RFQ information submitted |\
> \| \`rfq.assets\_uploaded\` | Assets uploaded to an RFQ |\
> \| \`rfq.copied\` | RFQ copied/duplicated |\
> \| \`rfq.cancelled\` | RFQ cancelled |\
> \
> \*\*Quote\*\*\
> \| Event | Trigger |\
> \|-------|---------|\
> \| \`quote.created\` | New quote created |\
> \| \`quote.status\_changed\` | Quote status changes (any transition) |\
> \| \`quote.sent\_to\_client\` | Quote sent to client |\
> \| \`quote.accepted\` | Quote accepted |\
> \| \`quote.rejected\` | Quote rejected |\
> \| \`quote.revised\_and\_sent\` | Revised quote re-sent |\
> \| \`quote.cancelled\` | Quote cancelled |\
> \| \`quote.converted\_to\_project\` | Quote converted into a project |\
> \
> \*\*Job\*\*\
> \| Event | Trigger |\
> \|-------|---------|\
> \| \`job.created\` | Job created |\
> \| \`job.started\` | Job started |\
> \| \`job.talent\_assigned\` | Talent assigned to a job |\
> \| \`job.request\_rejected\` | Talent rejected a job request |\
> \| \`job.reassigned\` | Job reassigned to a new talent |\
> \| \`job.delivery\_submitted\` | Delivery submitted |\
> \| \`job.delivery\_updated\` | Delivery updated |\
> \| \`job.delivery\_approved\` | Delivery approved |\
> \| \`job.delivery\_rejected\` | Delivery rejected (feedback) |\
> \| \`job.feedback\_submitted\` | Feedback submitted (not yet emitted) |\
> \| \`job.approved\_for\_invoicing\` | Job approved for invoicing |\
> \| \`job.archived\` | Job archived (not yet emitted) |\
> \| \`job.cancelled\` | Job cancelled |\
> \
> \*\*Project\*\*\
> \| Event | Trigger |\
> \|-------|---------|\
> \| \`project.status\_changed\` | Project status changes (any transition) |\
> \| \`project.created\` | Project created |\
> \| \`project.team\_updated\` | Project team or owner updated |\
> \| \`project.sent\_for\_billing\` | Project sent for billing |\
> \| \`project.delivered\` | Project delivered |\
> \| \`project.copied\` | Project copied/duplicated |\
> \| \`project.cancelled\` | Project cancelled |\
> \
> \### Verifying Callbacks\
> Each callback includes an \`X-Webhook-Signature\` header containing an HMAC-SHA256 of the raw JSON body using your secret.

````json
{"openapi":"3.1.0","info":{"title":"Adaptria Public API: Client","version":"1.0.0"},"tags":[{"name":"🔔 Webhooks","description":"## Webhooks\n\nReceive **HTTPS POST callbacks** when RFQs, quotes, jobs, or projects change for a company you select at registration.\n\n### Supported Events\n**RFQ**: `rfq.status_changed`, `rfq.submitted`, `rfq.info_submitted`, `rfq.assets_uploaded`, `rfq.copied`, `rfq.cancelled`\n\n**Quote**: `quote.created`, `quote.status_changed`, `quote.sent_to_client`, `quote.accepted`, `quote.rejected`, `quote.revised_and_sent`, `quote.cancelled`, `quote.converted_to_project`\n\n**Job**: `job.created`, `job.started`, `job.talent_assigned`, `job.request_rejected`, `job.reassigned`, `job.delivery_submitted`, `job.delivery_updated`, `job.delivery_approved`, `job.delivery_rejected`, `job.approved_for_invoicing`, `job.cancelled` (`job.feedback_submitted`, `job.archived`: reserved, not yet emitted)\n\n**Project**: `project.status_changed`, `project.created`, `project.team_updated`, `project.sent_for_billing`, `project.delivered`, `project.copied`, `project.cancelled`\n\n### How It Works\n1. **Register** a webhook URL + company scope → receive a signing secret (shown once)\n2. When events occur, we POST to your URL with `X-Webhook-Signature` (HMAC-SHA256) and `X-Webhook-Event` headers\n3. One active registration per API user — re-registering overwrites the previous webhook\n\n### Verifying Signatures\n```javascript\nconst crypto = require('crypto');\nconst signature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');\nconst expected = Buffer.from(signature, 'utf8');\nconst received = Buffer.from(req.headers['x-webhook-signature'], 'utf8');\nconst isValid = expected.length === received.length && crypto.timingSafeEqual(expected, received);\n```\n"}],"servers":[{"url":"https://api.adaptria.locaria.com","description":"Production Environment"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"Api-key","description":"## API Key Authentication\n\nStatic API key obtained from the Adaptria Portal or via `POST /api/credentials/api-keys`.\n\n**How to authenticate**:\n1. Generate an API key from the Adaptria Portal (Settings → API Keys) or ask your admin\n2. Include the key in all requests using the `Api-key` header\n\n**Key format**: `lla_live_<32-character-string>`\n\n**No token exchange needed** — the key is used directly in every request. Revoke or rotate keys from the portal."}},"schemas":{"RegisterWebhookRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL to receive webhook callbacks. Must use HTTPS."}},"required":["url"]},"WebhookRegistrationResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Webhook registration ID"},"url":{"type":"string","format":"uri","description":"Registered callback URL"},"secret":{"type":"string","description":"Signing secret for verifying webhook payloads. Shown only once at registration time."},"isActive":{"type":"boolean","description":"Whether the webhook is active"},"createdAt":{"type":"string","description":"Registration timestamp"}},"required":["id","url","secret","isActive","createdAt"]}}},"paths":{"/api/public/v1/webhooks":{"post":{"summary":"Register or replace webhook","description":"Register a webhook URL to receive HTTPS POST callbacks when events occur in any company the authenticated user belongs to.\n\n**One active registration per API user** — re-registering overwrites the previous webhook.\n\nThe signing `secret` is returned **only once** at registration time. Store it securely to verify inbound callbacks.\n\n### Supported Events\n**RFQ**\n| Event | Trigger |\n|-------|---------|\n| `rfq.status_changed` | RFQ status changes (any transition) |\n| `rfq.submitted` | RFQ submitted (DRAFT → PENDING) |\n| `rfq.info_submitted` | RFQ information submitted |\n| `rfq.assets_uploaded` | Assets uploaded to an RFQ |\n| `rfq.copied` | RFQ copied/duplicated |\n| `rfq.cancelled` | RFQ cancelled |\n\n**Quote**\n| Event | Trigger |\n|-------|---------|\n| `quote.created` | New quote created |\n| `quote.status_changed` | Quote status changes (any transition) |\n| `quote.sent_to_client` | Quote sent to client |\n| `quote.accepted` | Quote accepted |\n| `quote.rejected` | Quote rejected |\n| `quote.revised_and_sent` | Revised quote re-sent |\n| `quote.cancelled` | Quote cancelled |\n| `quote.converted_to_project` | Quote converted into a project |\n\n**Job**\n| Event | Trigger |\n|-------|---------|\n| `job.created` | Job created |\n| `job.started` | Job started |\n| `job.talent_assigned` | Talent assigned to a job |\n| `job.request_rejected` | Talent rejected a job request |\n| `job.reassigned` | Job reassigned to a new talent |\n| `job.delivery_submitted` | Delivery submitted |\n| `job.delivery_updated` | Delivery updated |\n| `job.delivery_approved` | Delivery approved |\n| `job.delivery_rejected` | Delivery rejected (feedback) |\n| `job.feedback_submitted` | Feedback submitted (not yet emitted) |\n| `job.approved_for_invoicing` | Job approved for invoicing |\n| `job.archived` | Job archived (not yet emitted) |\n| `job.cancelled` | Job cancelled |\n\n**Project**\n| Event | Trigger |\n|-------|---------|\n| `project.status_changed` | Project status changes (any transition) |\n| `project.created` | Project created |\n| `project.team_updated` | Project team or owner updated |\n| `project.sent_for_billing` | Project sent for billing |\n| `project.delivered` | Project delivered |\n| `project.copied` | Project copied/duplicated |\n| `project.cancelled` | Project cancelled |\n\n### Verifying Callbacks\nEach callback includes an `X-Webhook-Signature` header containing an HMAC-SHA256 of the raw JSON body using your secret.","tags":["🔔 Webhooks"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookRequest"}}}},"responses":{"201":{"description":"Webhook registered successfully. Secret is shown only once.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/WebhookRegistrationResponse"}},"required":["success","data"]}}}},"422":{"description":"Validation error — invalid URL"}}}}}}
````

## Remove webhook registration

> Removes the active webhook registration for the authenticated API user. Callbacks will stop immediately.

````json
{"openapi":"3.1.0","info":{"title":"Adaptria Public API: Client","version":"1.0.0"},"tags":[{"name":"🔔 Webhooks","description":"## Webhooks\n\nReceive **HTTPS POST callbacks** when RFQs, quotes, jobs, or projects change for a company you select at registration.\n\n### Supported Events\n**RFQ**: `rfq.status_changed`, `rfq.submitted`, `rfq.info_submitted`, `rfq.assets_uploaded`, `rfq.copied`, `rfq.cancelled`\n\n**Quote**: `quote.created`, `quote.status_changed`, `quote.sent_to_client`, `quote.accepted`, `quote.rejected`, `quote.revised_and_sent`, `quote.cancelled`, `quote.converted_to_project`\n\n**Job**: `job.created`, `job.started`, `job.talent_assigned`, `job.request_rejected`, `job.reassigned`, `job.delivery_submitted`, `job.delivery_updated`, `job.delivery_approved`, `job.delivery_rejected`, `job.approved_for_invoicing`, `job.cancelled` (`job.feedback_submitted`, `job.archived`: reserved, not yet emitted)\n\n**Project**: `project.status_changed`, `project.created`, `project.team_updated`, `project.sent_for_billing`, `project.delivered`, `project.copied`, `project.cancelled`\n\n### How It Works\n1. **Register** a webhook URL + company scope → receive a signing secret (shown once)\n2. When events occur, we POST to your URL with `X-Webhook-Signature` (HMAC-SHA256) and `X-Webhook-Event` headers\n3. One active registration per API user — re-registering overwrites the previous webhook\n\n### Verifying Signatures\n```javascript\nconst crypto = require('crypto');\nconst signature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');\nconst expected = Buffer.from(signature, 'utf8');\nconst received = Buffer.from(req.headers['x-webhook-signature'], 'utf8');\nconst isValid = expected.length === received.length && crypto.timingSafeEqual(expected, received);\n```\n"}],"servers":[{"url":"https://api.adaptria.locaria.com","description":"Production Environment"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"Api-key","description":"## API Key Authentication\n\nStatic API key obtained from the Adaptria Portal or via `POST /api/credentials/api-keys`.\n\n**How to authenticate**:\n1. Generate an API key from the Adaptria Portal (Settings → API Keys) or ask your admin\n2. Include the key in all requests using the `Api-key` header\n\n**Key format**: `lla_live_<32-character-string>`\n\n**No token exchange needed** — the key is used directly in every request. Revoke or rotate keys from the portal."}}},"paths":{"/api/public/v1/webhooks":{"delete":{"summary":"Remove webhook registration","description":"Removes the active webhook registration for the authenticated API user. Callbacks will stop immediately.","tags":["🔔 Webhooks"],"responses":{"204":{"description":"Webhook removed successfully"},"404":{"description":"No active webhook found"}}}}}}
````
