# Hooks

External service hooks for subscription management

## Bulk unsubscribe users (external service hook)

> External service hook for bulk unsubscribing users from\
> communication channels. Requires HMAC hash validation for security.\
> \
> \*\*Security:\*\*\
> \- Public endpoint secured via hash validation\
> \- Throttled with scope \`user-unsubscribe\`\
> \
> \*\*Hash Validation:\*\*\
> Hash is calculated via subscription gateway's hash generation method.\
> The gateway is retrieved from dynamic settings (\`ACTIVE\_SUBSCRIPTION\_GATEWAYS\` or \`SUBSCRIPTION\_GATEWAY\`).\
> If gateway doesn't implement hash generation, validation fails.\
> \
> \*\*User Matching Logic:\*\*\
> \- Users identified by email match against primary email OR verified email addresses\
> \- Only sets permission fields (\`email\_allowed\`, \`sms\_allowed\`, \`call\_allowed\`) that are explicitly \`False\`\
> \- Fields set to \`True\` are ignored (no change to user)\
> \- Skips users not found in database (no error raised)\
> \
> \*\*Side Effects:\*\*\
> \- Updates user's \`email\_allowed\`, \`sms\_allowed\`, and/or \`call\_allowed\` fields for matched users\
> \- Uses database transaction to ensure all-or-nothing updates\
> \
> \*\*Validation:\*\*\
> \- Request datetime must be within 1 minute of server time (60 seconds)\
> \- Maximum 100 users per request\
> \- Hash must match calculated hash from subscription gateway\
> \- At least one user required in \`unsubscribed\_users\` array\
> \
> \*\*Error Responses:\*\*\
> \- \`400\`: "Hash mismatch error" - Invalid hash\_value or gateway not configured\
> \- \`400\`: "Time gap error" - request\_datetime more than 1 minute old\
> \- \`400\`: "Ensure unsubscribed\_users field has at most 100 items." - Array too large\
> \
> Maximum 100 users per request.\
> Throttling scope: \`user-unsubscribe\`.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Hooks","description":"External service hooks for subscription management"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"paths":{"/users/hooks/unsubscribe-user/":{"patch":{"tags":["Hooks"],"operationId":"unsubscribeUsers","summary":"Bulk unsubscribe users (external service hook)","description":"External service hook for bulk unsubscribing users from\ncommunication channels. Requires HMAC hash validation for security.\n\n**Security:**\n- Public endpoint secured via hash validation\n- Throttled with scope `user-unsubscribe`\n\n**Hash Validation:**\nHash is calculated via subscription gateway's hash generation method.\nThe gateway is retrieved from dynamic settings (`ACTIVE_SUBSCRIPTION_GATEWAYS` or `SUBSCRIPTION_GATEWAY`).\nIf gateway doesn't implement hash generation, validation fails.\n\n**User Matching Logic:**\n- Users identified by email match against primary email OR verified email addresses\n- Only sets permission fields (`email_allowed`, `sms_allowed`, `call_allowed`) that are explicitly `False`\n- Fields set to `True` are ignored (no change to user)\n- Skips users not found in database (no error raised)\n\n**Side Effects:**\n- Updates user's `email_allowed`, `sms_allowed`, and/or `call_allowed` fields for matched users\n- Uses database transaction to ensure all-or-nothing updates\n\n**Validation:**\n- Request datetime must be within 1 minute of server time (60 seconds)\n- Maximum 100 users per request\n- Hash must match calculated hash from subscription gateway\n- At least one user required in `unsubscribed_users` array\n\n**Error Responses:**\n- `400`: \"Hash mismatch error\" - Invalid hash_value or gateway not configured\n- `400`: \"Time gap error\" - request_datetime more than 1 minute old\n- `400`: \"Ensure unsubscribed_users field has at most 100 items.\" - Array too large\n\nMaximum 100 users per request.\nThrottling scope: `user-unsubscribe`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersUnsubscriptionRequest"}}}},"responses":{"200":{"description":"Unsubscription processed successfully. Users' communication preferences updated.\nReturns empty response body with 200 status."},"400":{"description":"Validation error, hash mismatch, or time gap error.\n\nCommon errors:\n- Hash mismatch error\n- Time gap error\n- Ensure unsubscribed_users field has at most 100 items.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"},"request_datetime":{"type":"array","items":{"type":"string"}},"unsubscribed_users":{"type":"array","items":{"type":"string"}}}}}}},"429":{"description":"Too many requests (throttled)"}}}}},"components":{"schemas":{"UsersUnsubscriptionRequest":{"type":"object","required":["service_name","hash_value","request_datetime","unsubscribed_users"],"properties":{"service_name":{"type":"string","maxLength":20,"description":"Identifier for the calling service. Must match a configured subscription gateway\nin `ACTIVE_SUBSCRIPTION_GATEWAYS` or `SUBSCRIPTION_GATEWAY` dynamic settings."},"hash_value":{"type":"string","description":"HMAC hash for request validation. Generated by the subscription gateway's\n`get_unsubscription_hash(request_datetime)` method. Algorithm depends on gateway implementation."},"request_datetime":{"type":"string","format":"date-time","description":"Request timestamp in ISO format. Must be within 1 minute of server time\nto be valid (time gap validated as: (now - request_datetime).seconds / 60 < 1)."},"unsubscribed_users":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/UserUnsubscription"}}}},"UserUnsubscription":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"User email address. Matches against primary email or verified email addresses.\nCan match multiple verified email entries for the same user."},"email_allowed":{"type":"boolean","description":"Set email communication preference. Only `False` value will update the user.\nIf `True` or omitted, no change is made to user's email_allowed field."},"sms_allowed":{"type":"boolean","description":"Set SMS communication preference. Only `False` value will update the user.\nIf `True` or omitted, no change is made to user's sms_allowed field."},"call_allowed":{"type":"boolean","description":"Set call communication preference. Only `False` value will update the user.\nIf `True` or omitted, no change is made to user's call_allowed field."}}}}}}
```

## KVKK-compliant bulk unsubscription

> External service hook for KVKK (Turkish Personal Data Protection Law)\
> compliant bulk unsubscription. Uses KVKK-specific hash validation\
> with SHA-256 algorithm.\
> \
> \*\*Security:\*\*\
> \- Public endpoint secured via SHA-256 hash validation\
> \- Throttled with scope \`user-unsubscribe\`\
> \- Creates audit events for compliance tracking\
> \
> \*\*Hash Validation:\*\*\
> \- Hash algorithm: SHA-256\
> \- Hash input: \`{secret\_key}{request\_datetime.isoformat()}\`\
> \- Secret keys configured via \`KVKK\_UNSUBSCRIPTION\_SECRET\_MAP\` setting (dict mapping service\_name to secret)\
> \- If setting not configured or service\_name not in map, validation fails\
> \
> \*\*User Identification:\*\*\
> \- Users can be identified by \`email\` OR \`phone\` (mutually exclusive, not both)\
> \- Email matching: against primary email OR verified email addresses\
> \- Phone matching: against user's phone number\
> \- If neither email nor phone provided, validation error\
> \- If both email and phone provided, validation error\
> \
> \*\*User Matching Logic:\*\*\
> \- Only sets permission fields (\`email\_allowed\`, \`sms\_allowed\`, \`call\_allowed\`) that are explicitly \`False\`\
> \- Fields set to \`True\` are ignored (no change to user)\
> \- Skips users not found in database (no error raised)\
> \- If no \`False\` values in permission fields, skips user (no update or audit event)\
> \
> \*\*Side Effects:\*\*\
> \- Updates user's \`email\_allowed\`, \`sms\_allowed\`, and/or \`call\_allowed\` fields for matched users\
> \- Creates audit event for each updated user for KVKK compliance\
> \- Audit event includes: service\_name and all permission flags from request\
> \- Uses database transaction to ensure all-or-nothing updates\
> \
> \*\*Validation:\*\*\
> \- Request datetime must be within 1 minute of server time (60 seconds)\
> \- Maximum 100 users per request\
> \- Hash must match SHA-256 hash: \`sha256(secret\_key + request\_datetime.isoformat()).hexdigest()\`\
> \- Each user must have exactly one of: email or phone\
> \- At least one user required in \`unsubscribed\_users\` array\
> \
> \*\*Error Responses:\*\*\
> \- \`400\`: "Hash mismatch error" - Invalid hash\_value, missing secret key, or KVKK\_UNSUBSCRIPTION\_SECRET\_MAP not configured\
> \- \`400\`: "Time gap error" - request\_datetime more than 1 minute old\
> \- \`400\`: "Only email or phone field acceptable" - Both email and phone provided\
> \- \`400\`: "User data must include email or phone  field" - Neither email nor phone provided\
> \- \`400\`: "Ensure unsubscribed\_users field has at most 100 items." - Array too large\
> \
> Maximum 100 users per request.\
> Throttling scope: \`user-unsubscribe\`.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Hooks","description":"External service hooks for subscription management"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"paths":{"/users/hooks/kvkk-unsubscribe-user/":{"patch":{"tags":["Hooks"],"operationId":"kvkkUnsubscribeUsers","summary":"KVKK-compliant bulk unsubscription","description":"External service hook for KVKK (Turkish Personal Data Protection Law)\ncompliant bulk unsubscription. Uses KVKK-specific hash validation\nwith SHA-256 algorithm.\n\n**Security:**\n- Public endpoint secured via SHA-256 hash validation\n- Throttled with scope `user-unsubscribe`\n- Creates audit events for compliance tracking\n\n**Hash Validation:**\n- Hash algorithm: SHA-256\n- Hash input: `{secret_key}{request_datetime.isoformat()}`\n- Secret keys configured via `KVKK_UNSUBSCRIPTION_SECRET_MAP` setting (dict mapping service_name to secret)\n- If setting not configured or service_name not in map, validation fails\n\n**User Identification:**\n- Users can be identified by `email` OR `phone` (mutually exclusive, not both)\n- Email matching: against primary email OR verified email addresses\n- Phone matching: against user's phone number\n- If neither email nor phone provided, validation error\n- If both email and phone provided, validation error\n\n**User Matching Logic:**\n- Only sets permission fields (`email_allowed`, `sms_allowed`, `call_allowed`) that are explicitly `False`\n- Fields set to `True` are ignored (no change to user)\n- Skips users not found in database (no error raised)\n- If no `False` values in permission fields, skips user (no update or audit event)\n\n**Side Effects:**\n- Updates user's `email_allowed`, `sms_allowed`, and/or `call_allowed` fields for matched users\n- Creates audit event for each updated user for KVKK compliance\n- Audit event includes: service_name and all permission flags from request\n- Uses database transaction to ensure all-or-nothing updates\n\n**Validation:**\n- Request datetime must be within 1 minute of server time (60 seconds)\n- Maximum 100 users per request\n- Hash must match SHA-256 hash: `sha256(secret_key + request_datetime.isoformat()).hexdigest()`\n- Each user must have exactly one of: email or phone\n- At least one user required in `unsubscribed_users` array\n\n**Error Responses:**\n- `400`: \"Hash mismatch error\" - Invalid hash_value, missing secret key, or KVKK_UNSUBSCRIPTION_SECRET_MAP not configured\n- `400`: \"Time gap error\" - request_datetime more than 1 minute old\n- `400`: \"Only email or phone field acceptable\" - Both email and phone provided\n- `400`: \"User data must include email or phone  field\" - Neither email nor phone provided\n- `400`: \"Ensure unsubscribed_users field has at most 100 items.\" - Array too large\n\nMaximum 100 users per request.\nThrottling scope: `user-unsubscribe`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KvkkUsersUnsubscriptionRequest"}}}},"responses":{"200":{"description":"Unsubscription processed successfully. Users' communication preferences updated.\nAudit events created for each user update.\nReturns empty response body with 200 status."},"400":{"description":"Validation error, hash mismatch, or time gap error.\n\nCommon errors:\n- Hash mismatch error\n- Time gap error\n- Only email or phone field acceptable\n- User data must include email or phone  field\n- Ensure unsubscribed_users field has at most 100 items.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"},"request_datetime":{"type":"array","items":{"type":"string"}},"unsubscribed_users":{"type":"object","properties":{"non_field_errors":{"type":"array","items":{"type":"string"}}}}}}}}},"429":{"description":"Too many requests (throttled)"}}}}},"components":{"schemas":{"KvkkUsersUnsubscriptionRequest":{"type":"object","required":["service_name","hash_value","request_datetime","unsubscribed_users"],"properties":{"service_name":{"type":"string","maxLength":20,"description":"Identifier for the calling service. Must have a corresponding secret key\nin `KVKK_UNSUBSCRIPTION_SECRET_MAP` setting."},"hash_value":{"type":"string","description":"KVKK-specific SHA-256 hash for request validation.\nCalculated as: `sha256(secret_key + request_datetime.isoformat()).hexdigest()`\nwhere secret_key is retrieved from KVKK_UNSUBSCRIPTION_SECRET_MAP[service_name]."},"request_datetime":{"type":"string","format":"date-time","description":"Request timestamp in ISO format. Must be within 1 minute of server time\n(time gap validated as: (now - request_datetime).seconds / 60 < 1)."},"unsubscribed_users":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/KvkkUserUnsubscription"}}}},"KvkkUserUnsubscription":{"type":"object","description":"User identification for KVKK unsubscription.\nMust provide either email OR phone, not both.\n\nAt least one permission field (email_allowed, sms_allowed, call_allowed) \nshould be set to False to trigger an update. Fields set to True are ignored.","properties":{"phone":{"type":"string","description":"User phone number (alternative to email)"},"email":{"type":"string","format":"email","description":"User email (alternative to phone)"},"email_allowed":{"type":"boolean","description":"Set email communication preference"},"sms_allowed":{"type":"boolean","description":"Set SMS communication preference"},"call_allowed":{"type":"boolean","description":"Set call communication preference"}}}}}}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://apidocs.akinon.com/commerce-openapis/users/module-3/hooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
